mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add runtime selection feature between Bun and Node.js
This commit is contained in:
5
.changeset/vast-plants-boil.md
Normal file
5
.changeset/vast-plants-boil.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add runtime selection feature between Bun and Node.js
|
||||||
@@ -19,6 +19,7 @@ Follow the prompts to configure your project.
|
|||||||
- **Monorepo**: Turborepo for optimized build system and workspace management
|
- **Monorepo**: Turborepo for optimized build system and workspace management
|
||||||
- **Frontend**: React, TanStack Router, TanStack Query, Tailwind CSS with shadcn/ui components
|
- **Frontend**: React, TanStack Router, TanStack Query, Tailwind CSS with shadcn/ui components
|
||||||
- **Backend**: Hono, tRPC
|
- **Backend**: Hono, tRPC
|
||||||
|
- **Runtime Options**: Choose between Bun or Node.js for your server
|
||||||
- **Database Options**: SQLite (via Turso), PostgreSQL, or no database
|
- **Database Options**: SQLite (via Turso), PostgreSQL, or no database
|
||||||
- **ORM Selection**: Choose between Drizzle ORM or Prisma
|
- **ORM Selection**: Choose between Drizzle ORM or Prisma
|
||||||
- **Authentication**: Optional auth setup with Better-Auth
|
- **Authentication**: Optional auth setup with Better-Auth
|
||||||
@@ -60,6 +61,7 @@ Options:
|
|||||||
--no-install Skip installing dependencies
|
--no-install Skip installing dependencies
|
||||||
--turso Set up Turso for SQLite database (default with sqlite)
|
--turso Set up Turso for SQLite database (default with sqlite)
|
||||||
--no-turso Skip Turso setup for SQLite database
|
--no-turso Skip Turso setup for SQLite database
|
||||||
|
--runtime <runtime> Specify runtime (bun or node)
|
||||||
-h, --help Display help
|
-h, --help Display help
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,6 +77,11 @@ Create a project with specific options:
|
|||||||
npx create-better-t-stack my-app --postgres --prisma --auth --pwa --biome
|
npx create-better-t-stack my-app --postgres --prisma --auth --pwa --biome
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Create a project with Node.js runtime:
|
||||||
|
```bash
|
||||||
|
npx create-better-t-stack my-app --runtime node
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
|||||||
orm: "drizzle",
|
orm: "drizzle",
|
||||||
auth: true,
|
auth: true,
|
||||||
addons: [],
|
addons: [],
|
||||||
|
examples: [],
|
||||||
git: true,
|
git: true,
|
||||||
packageManager: "npm",
|
packageManager: "npm",
|
||||||
noInstall: false,
|
noInstall: false,
|
||||||
examples: ["todo"],
|
turso: false,
|
||||||
|
backendFramework: "hono",
|
||||||
|
runtime: "bun",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dependencyVersionMap = {
|
export const dependencyVersionMap = {
|
||||||
@@ -39,6 +42,12 @@ export const dependencyVersionMap = {
|
|||||||
|
|
||||||
husky: "^9.1.7",
|
husky: "^9.1.7",
|
||||||
"lint-staged": "^15.5.0",
|
"lint-staged": "^15.5.0",
|
||||||
|
|
||||||
|
"@hono/node-server": "^1.14.0",
|
||||||
|
tsx: "^4.19.2",
|
||||||
|
"@types/node": "^22.13.11",
|
||||||
|
|
||||||
|
"@types/bun": "^1.2.6",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { setupEnvironmentVariables } from "./env-setup";
|
|||||||
import { setupExamples } from "./examples-setup";
|
import { setupExamples } from "./examples-setup";
|
||||||
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 {
|
import {
|
||||||
copyBaseTemplate,
|
copyBaseTemplate,
|
||||||
fixGitignoreFiles,
|
fixGitignoreFiles,
|
||||||
@@ -38,6 +39,8 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
options.auth,
|
options.auth,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await setupRuntime(projectDir, options.runtime);
|
||||||
|
|
||||||
await setupExamples(
|
await setupExamples(
|
||||||
projectDir,
|
projectDir,
|
||||||
options.examples,
|
options.examples,
|
||||||
@@ -73,6 +76,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
!options.noInstall,
|
!options.noInstall,
|
||||||
options.orm,
|
options.orm,
|
||||||
options.addons,
|
options.addons,
|
||||||
|
options.runtime,
|
||||||
);
|
);
|
||||||
|
|
||||||
return projectDir;
|
return projectDir;
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import type { ProjectConfig, ProjectDatabase, ProjectOrm } from "../types";
|
import type {
|
||||||
|
ProjectAddons,
|
||||||
|
ProjectConfig,
|
||||||
|
ProjectDatabase,
|
||||||
|
ProjectOrm,
|
||||||
|
Runtime,
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
export async function createReadme(projectDir: string, options: ProjectConfig) {
|
export async function createReadme(projectDir: string, options: ProjectConfig) {
|
||||||
const readmePath = path.join(projectDir, "README.md");
|
const readmePath = path.join(projectDir, "README.md");
|
||||||
@@ -21,6 +27,7 @@ function generateReadmeContent(options: ProjectConfig): string {
|
|||||||
auth,
|
auth,
|
||||||
addons = [],
|
addons = [],
|
||||||
orm = "drizzle",
|
orm = "drizzle",
|
||||||
|
runtime = "bun",
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const packageManagerRunCmd =
|
const packageManagerRunCmd =
|
||||||
@@ -32,7 +39,7 @@ This project was created with [Better-T-Stack](https://github.com/better-t-stack
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
${generateFeaturesList(database, auth, addons, orm)}
|
${generateFeaturesList(database, auth, addons, orm, runtime)}
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -71,38 +78,46 @@ ${generateScriptsList(packageManagerRunCmd, database, orm, auth)}
|
|||||||
function generateFeaturesList(
|
function generateFeaturesList(
|
||||||
database: ProjectDatabase,
|
database: ProjectDatabase,
|
||||||
auth: boolean,
|
auth: boolean,
|
||||||
features: string[],
|
addons: ProjectAddons[],
|
||||||
orm: ProjectOrm,
|
orm: ProjectOrm,
|
||||||
|
runtime: Runtime,
|
||||||
): string {
|
): string {
|
||||||
const featuresList = [
|
const addonsList = [
|
||||||
"- **TypeScript** - For type safety and improved developer experience",
|
"- **TypeScript** - For type safety and improved developer experience",
|
||||||
"- **TanStack Router** - File-based routing with full type safety",
|
"- **TanStack Router** - File-based routing with full type safety",
|
||||||
"- **TailwindCSS** - Utility-first CSS for rapid UI development",
|
"- **TailwindCSS** - Utility-first CSS for rapid UI development",
|
||||||
"- **shadcn/ui** - Reusable UI components",
|
"- **shadcn/ui** - Reusable UI components",
|
||||||
"- **Hono** - Lightweight, performant server framework",
|
"- **Hono** - Lightweight, performant server framework",
|
||||||
"- **tRPC** - End-to-end type-safe APIs",
|
"- **tRPC** - End-to-end type-safe APIs",
|
||||||
|
`- **${runtime === "bun" ? "Bun" : "Node.js"}** - Runtime environment`,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (database !== "none") {
|
if (database !== "none") {
|
||||||
featuresList.push(
|
addonsList.push(
|
||||||
`- **${orm === "drizzle" ? "Drizzle" : "Prisma"}** - TypeScript-first ORM`,
|
`- **${orm === "drizzle" ? "Drizzle" : "Prisma"}** - TypeScript-first ORM`,
|
||||||
`- **${database === "sqlite" ? "SQLite/Turso" : "PostgreSQL"}** - Database engine`,
|
`- **${database === "sqlite" ? "SQLite/Turso" : "PostgreSQL"}** - Database engine`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth) {
|
if (auth) {
|
||||||
featuresList.push(
|
addonsList.push(
|
||||||
"- **Authentication** - Email & password authentication with Better Auth",
|
"- **Authentication** - Email & password authentication with Better Auth",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const feature of features) {
|
for (const addon of addons) {
|
||||||
if (feature === "docker") {
|
if (addon === "pwa") {
|
||||||
featuresList.push("- **Docker** - Containerized deployment");
|
addonsList.push("- **PWA** - Progressive Web App support");
|
||||||
|
} else if (addon === "tauri") {
|
||||||
|
addonsList.push("- **Tauri** - Build native desktop applications");
|
||||||
|
} else if (addon === "biome") {
|
||||||
|
addonsList.push("- **Biome** - Linting and formatting");
|
||||||
|
} else if (addon === "husky") {
|
||||||
|
addonsList.push("- **Husky** - Git hooks for code quality");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return featuresList.join("\n");
|
return addonsList.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDatabaseSetup(
|
function generateDatabaseSetup(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
ProjectDatabase,
|
ProjectDatabase,
|
||||||
ProjectOrm,
|
ProjectOrm,
|
||||||
|
Runtime,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
export function displayPostInstallInstructions(
|
export function displayPostInstallInstructions(
|
||||||
@@ -14,6 +15,7 @@ export function displayPostInstallInstructions(
|
|||||||
depsInstalled: boolean,
|
depsInstalled: boolean,
|
||||||
orm?: ProjectOrm,
|
orm?: ProjectOrm,
|
||||||
addons?: ProjectAddons[],
|
addons?: ProjectAddons[],
|
||||||
|
runtime?: Runtime,
|
||||||
) {
|
) {
|
||||||
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
||||||
const cdCmd = `cd ${projectName}`;
|
const cdCmd = `cd ${projectName}`;
|
||||||
@@ -21,7 +23,9 @@ export function displayPostInstallInstructions(
|
|||||||
addons?.includes("husky") || addons?.includes("biome");
|
addons?.includes("husky") || addons?.includes("biome");
|
||||||
|
|
||||||
const databaseInstructions =
|
const databaseInstructions =
|
||||||
database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : "";
|
database !== "none"
|
||||||
|
? getDatabaseInstructions(database, orm, runCmd, runtime)
|
||||||
|
: "";
|
||||||
const tauriInstructions = addons?.includes("tauri")
|
const tauriInstructions = addons?.includes("tauri")
|
||||||
? getTauriInstructions(runCmd)
|
? getTauriInstructions(runCmd)
|
||||||
: "";
|
: "";
|
||||||
@@ -49,6 +53,7 @@ function getDatabaseInstructions(
|
|||||||
database: ProjectDatabase,
|
database: ProjectDatabase,
|
||||||
orm?: ProjectOrm,
|
orm?: ProjectOrm,
|
||||||
runCmd?: string,
|
runCmd?: string,
|
||||||
|
runtime?: Runtime,
|
||||||
): string {
|
): string {
|
||||||
const instructions = [];
|
const instructions = [];
|
||||||
|
|
||||||
@@ -59,6 +64,13 @@ function getDatabaseInstructions(
|
|||||||
`${pc.dim("Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso")}`,
|
`${pc.dim("Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso")}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runtime === "bun") {
|
||||||
|
instructions.push(
|
||||||
|
`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors, follow the guidance provided in the error messages`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(
|
||||||
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
|
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
|
||||||
);
|
);
|
||||||
|
|||||||
94
apps/cli/src/helpers/runtime-setup.ts
Normal file
94
apps/cli/src/helpers/runtime-setup.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import type { Runtime } from "../types";
|
||||||
|
import { addPackageDependency } from "../utils/add-package-deps";
|
||||||
|
|
||||||
|
export async function setupRuntime(
|
||||||
|
projectDir: string,
|
||||||
|
runtime: Runtime,
|
||||||
|
): Promise<void> {
|
||||||
|
const serverDir = path.join(projectDir, "apps/server");
|
||||||
|
const serverIndexPath = path.join(serverDir, "src/index.ts");
|
||||||
|
|
||||||
|
const indexContent = await fs.readFile(serverIndexPath, "utf-8");
|
||||||
|
|
||||||
|
if (runtime === "bun") {
|
||||||
|
await setupBunRuntime(serverDir, serverIndexPath, indexContent);
|
||||||
|
} else if (runtime === "node") {
|
||||||
|
await setupNodeRuntime(serverDir, serverIndexPath, indexContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupBunRuntime(
|
||||||
|
serverDir: string,
|
||||||
|
serverIndexPath: string,
|
||||||
|
indexContent: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
|
packageJson.scripts = {
|
||||||
|
...packageJson.scripts,
|
||||||
|
dev: "bun run --hot src/index.ts",
|
||||||
|
start: "bun run dist/src/index.js",
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
|
|
||||||
|
addPackageDependency({
|
||||||
|
devDependencies: ["@types/bun"],
|
||||||
|
projectDir: serverDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!indexContent.includes("export default app")) {
|
||||||
|
const updatedContent = `${indexContent}\n\nexport default app;\n`;
|
||||||
|
await fs.writeFile(serverIndexPath, updatedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupNodeRuntime(
|
||||||
|
serverDir: string,
|
||||||
|
serverIndexPath: string,
|
||||||
|
indexContent: string,
|
||||||
|
): Promise<void> {
|
||||||
|
addPackageDependency({
|
||||||
|
dependencies: ["@hono/node-server"],
|
||||||
|
devDependencies: ["tsx", "@types/node"],
|
||||||
|
projectDir: serverDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
|
packageJson.scripts = {
|
||||||
|
...packageJson.scripts,
|
||||||
|
dev: "tsx watch src/index.ts",
|
||||||
|
start: "node dist/src/index.js",
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
|
|
||||||
|
const importLine = 'import { serve } from "@hono/node-server";\n';
|
||||||
|
const serverCode = `
|
||||||
|
serve(
|
||||||
|
{
|
||||||
|
fetch: app.fetch,
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
(info) => {
|
||||||
|
console.log(\`Server is running on http://localhost:\${info.port}\`);
|
||||||
|
},
|
||||||
|
);\n`;
|
||||||
|
|
||||||
|
if (!indexContent.includes("@hono/node-server")) {
|
||||||
|
const importEndIndex = indexContent.lastIndexOf("import");
|
||||||
|
const importSection = indexContent.substring(0, importEndIndex);
|
||||||
|
const restOfFile = indexContent.substring(importEndIndex);
|
||||||
|
|
||||||
|
const updatedContent = importSection + importLine + restOfFile + serverCode;
|
||||||
|
await fs.writeFile(serverIndexPath, updatedContent);
|
||||||
|
} else if (!indexContent.includes("serve(")) {
|
||||||
|
const updatedContent = indexContent + serverCode;
|
||||||
|
await fs.writeFile(serverIndexPath, updatedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,12 @@ import { DEFAULT_CONFIG } from "./constants";
|
|||||||
import { createProject } from "./helpers/create-project";
|
import { createProject } from "./helpers/create-project";
|
||||||
import { installDependencies } from "./helpers/install-dependencies";
|
import { installDependencies } from "./helpers/install-dependencies";
|
||||||
import { gatherConfig } from "./prompts/config-prompts";
|
import { gatherConfig } from "./prompts/config-prompts";
|
||||||
import type { ProjectAddons, ProjectConfig, ProjectExamples } from "./types";
|
import type {
|
||||||
|
ProjectAddons,
|
||||||
|
ProjectConfig,
|
||||||
|
ProjectExamples,
|
||||||
|
Runtime,
|
||||||
|
} 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";
|
||||||
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
||||||
@@ -50,6 +55,8 @@ async function main() {
|
|||||||
.option("--no-install", "Skip installing dependencies")
|
.option("--no-install", "Skip installing dependencies")
|
||||||
.option("--turso", "Set up Turso for SQLite database")
|
.option("--turso", "Set up Turso for SQLite database")
|
||||||
.option("--no-turso", "Skip Turso setup for SQLite database")
|
.option("--no-turso", "Skip Turso setup for SQLite database")
|
||||||
|
.option("--hono", "Use Hono backend framework")
|
||||||
|
.option("--runtime <runtime>", "Specify runtime (bun or node)")
|
||||||
.parse();
|
.parse();
|
||||||
|
|
||||||
const s = spinner();
|
const s = spinner();
|
||||||
@@ -70,11 +77,13 @@ async function main() {
|
|||||||
...(options.prisma && { orm: "prisma" }),
|
...(options.prisma && { orm: "prisma" }),
|
||||||
...("auth" in options && { auth: options.auth }),
|
...("auth" in options && { auth: options.auth }),
|
||||||
...(options.npm && { packageManager: "npm" }),
|
...(options.npm && { packageManager: "npm" }),
|
||||||
...(options.pnpm && { packageManager: " pnpm" }),
|
...(options.pnpm && { packageManager: "pnpm" }),
|
||||||
...(options.bun && { packageManager: "bun" }),
|
...(options.bun && { packageManager: "bun" }),
|
||||||
...("git" in options && { git: options.git }),
|
...("git" in options && { git: options.git }),
|
||||||
...("install" in options && { noInstall: !options.install }),
|
...("install" in options && { noInstall: !options.install }),
|
||||||
...("turso" in options && { turso: options.turso }),
|
...("turso" in options && { turso: options.turso }),
|
||||||
|
...(options.hono && { backendFramework: "hono" }),
|
||||||
|
...(options.runtime && { runtime: options.runtime as Runtime }),
|
||||||
...((options.pwa ||
|
...((options.pwa ||
|
||||||
options.tauri ||
|
options.tauri ||
|
||||||
options.biome ||
|
options.biome ||
|
||||||
@@ -144,6 +153,12 @@ async function main() {
|
|||||||
: flagConfig.database === "sqlite"
|
: flagConfig.database === "sqlite"
|
||||||
? DEFAULT_CONFIG.turso
|
? DEFAULT_CONFIG.turso
|
||||||
: false,
|
: false,
|
||||||
|
backendFramework: options.hono
|
||||||
|
? "hono"
|
||||||
|
: DEFAULT_CONFIG.backendFramework,
|
||||||
|
runtime: options.runtime
|
||||||
|
? (options.runtime as Runtime)
|
||||||
|
: DEFAULT_CONFIG.runtime,
|
||||||
}
|
}
|
||||||
: await gatherConfig(flagConfig);
|
: await gatherConfig(flagConfig);
|
||||||
|
|
||||||
|
|||||||
30
apps/cli/src/prompts/backend-framework.ts
Normal file
30
apps/cli/src/prompts/backend-framework.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
|
// import pc from "picocolors";
|
||||||
|
import type { BackendFramework } from "../types";
|
||||||
|
|
||||||
|
export async function getBackendFrameworkChoice(
|
||||||
|
backendFramework?: BackendFramework,
|
||||||
|
): Promise<BackendFramework> {
|
||||||
|
if (backendFramework !== undefined) return backendFramework;
|
||||||
|
|
||||||
|
return "hono";
|
||||||
|
|
||||||
|
// const response = await select<BackendFramework>({
|
||||||
|
// message: "Which backend framework would you like to use?",
|
||||||
|
// options: [
|
||||||
|
// {
|
||||||
|
// value: "hono",
|
||||||
|
// label: "Hono",
|
||||||
|
// hint: "Lightweight, ultrafast web framework",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// initialValue: "hono",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (isCancel(response)) {
|
||||||
|
// cancel(pc.red("Operation cancelled"));
|
||||||
|
// process.exit(0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return response;
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import { cancel, group } from "@clack/prompts";
|
import { cancel, group } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import type {
|
import type {
|
||||||
|
BackendFramework,
|
||||||
PackageManager,
|
PackageManager,
|
||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
ProjectDatabase,
|
ProjectDatabase,
|
||||||
ProjectExamples,
|
ProjectExamples,
|
||||||
ProjectOrm,
|
ProjectOrm,
|
||||||
|
Runtime,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { getAddonsChoice } from "./addons";
|
import { getAddonsChoice } from "./addons";
|
||||||
import { getAuthChoice } from "./auth";
|
import { getAuthChoice } from "./auth";
|
||||||
|
import { getBackendFrameworkChoice } from "./backend-framework";
|
||||||
import { getDatabaseChoice } from "./database";
|
import { getDatabaseChoice } from "./database";
|
||||||
import { getExamplesChoice } from "./examples";
|
import { getExamplesChoice } from "./examples";
|
||||||
import { getGitChoice } from "./git";
|
import { getGitChoice } from "./git";
|
||||||
@@ -17,9 +20,10 @@ import { getNoInstallChoice } from "./install";
|
|||||||
import { getORMChoice } from "./orm";
|
import { getORMChoice } from "./orm";
|
||||||
import { getPackageManagerChoice } from "./package-manager";
|
import { getPackageManagerChoice } from "./package-manager";
|
||||||
import { getProjectName } from "./project-name";
|
import { getProjectName } from "./project-name";
|
||||||
|
import { getRuntimeChoice } from "./runtime";
|
||||||
import { getTursoSetupChoice } from "./turso";
|
import { getTursoSetupChoice } from "./turso";
|
||||||
|
|
||||||
interface PromptGroupResults {
|
type PromptGroupResults = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
database: ProjectDatabase;
|
database: ProjectDatabase;
|
||||||
orm: ProjectOrm;
|
orm: ProjectOrm;
|
||||||
@@ -30,7 +34,9 @@ interface PromptGroupResults {
|
|||||||
packageManager: PackageManager;
|
packageManager: PackageManager;
|
||||||
noInstall: boolean;
|
noInstall: boolean;
|
||||||
turso: boolean;
|
turso: boolean;
|
||||||
}
|
backendFramework: BackendFramework;
|
||||||
|
runtime: Runtime;
|
||||||
|
};
|
||||||
|
|
||||||
export async function gatherConfig(
|
export async function gatherConfig(
|
||||||
flags: Partial<ProjectConfig>,
|
flags: Partial<ProjectConfig>,
|
||||||
@@ -40,6 +46,8 @@ export async function gatherConfig(
|
|||||||
projectName: async () => {
|
projectName: async () => {
|
||||||
return getProjectName(flags.projectName);
|
return getProjectName(flags.projectName);
|
||||||
},
|
},
|
||||||
|
runtime: () => getRuntimeChoice(flags.runtime),
|
||||||
|
backendFramework: () => getBackendFrameworkChoice(flags.backendFramework),
|
||||||
database: () => getDatabaseChoice(flags.database),
|
database: () => getDatabaseChoice(flags.database),
|
||||||
orm: ({ results }) =>
|
orm: ({ results }) =>
|
||||||
getORMChoice(flags.orm, results.database !== "none"),
|
getORMChoice(flags.orm, results.database !== "none"),
|
||||||
@@ -75,5 +83,7 @@ export async function gatherConfig(
|
|||||||
packageManager: result.packageManager,
|
packageManager: result.packageManager,
|
||||||
noInstall: result.noInstall,
|
noInstall: result.noInstall,
|
||||||
turso: result.turso,
|
turso: result.turso,
|
||||||
|
backendFramework: result.backendFramework,
|
||||||
|
runtime: result.runtime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cancel, isCancel, select } from "@clack/prompts";
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import type { PackageManager } from "../types";
|
import type { PackageManager, Runtime } from "../types";
|
||||||
import { getUserPkgManager } from "../utils/get-package-manager";
|
import { getUserPkgManager } from "../utils/get-package-manager";
|
||||||
|
|
||||||
export async function getPackageManagerChoice(
|
export async function getPackageManagerChoice(
|
||||||
|
|||||||
31
apps/cli/src/prompts/runtime.ts
Normal file
31
apps/cli/src/prompts/runtime.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
|
import pc from "picocolors";
|
||||||
|
import type { Runtime } from "../types";
|
||||||
|
|
||||||
|
export async function getRuntimeChoice(runtime?: Runtime): Promise<Runtime> {
|
||||||
|
if (runtime !== undefined) return runtime;
|
||||||
|
|
||||||
|
const response = await select<Runtime>({
|
||||||
|
message: "Which runtime would you like to use?",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "bun",
|
||||||
|
label: "Bun",
|
||||||
|
hint: "Fast all-in-one JavaScript runtime",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "node",
|
||||||
|
label: "Node.js",
|
||||||
|
hint: "Traditional Node.js runtime",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initialValue: "bun",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isCancel(response)) {
|
||||||
|
cancel(pc.red("Operation cancelled"));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ export type ProjectOrm = "drizzle" | "prisma" | "none";
|
|||||||
export type PackageManager = "npm" | "pnpm" | "bun";
|
export type PackageManager = "npm" | "pnpm" | "bun";
|
||||||
export type ProjectAddons = "pwa" | "tauri" | "biome" | "husky";
|
export type ProjectAddons = "pwa" | "tauri" | "biome" | "husky";
|
||||||
export type ProjectExamples = "todo";
|
export type ProjectExamples = "todo";
|
||||||
|
export type BackendFramework = "hono";
|
||||||
|
export type Runtime = "bun" | "node";
|
||||||
|
|
||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -15,4 +17,6 @@ export interface ProjectConfig {
|
|||||||
packageManager: PackageManager;
|
packageManager: PackageManager;
|
||||||
noInstall?: boolean;
|
noInstall?: boolean;
|
||||||
turso?: boolean;
|
turso?: boolean;
|
||||||
|
backendFramework: BackendFramework;
|
||||||
|
runtime: Runtime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
|||||||
if (config.auth !== undefined) {
|
if (config.auth !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
|
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
|
||||||
}
|
}
|
||||||
|
if (config.runtime) {
|
||||||
|
configDisplay.push(`${pc.blue("Runtime:")} ${config.runtime}`);
|
||||||
|
}
|
||||||
if (config.addons?.length) {
|
if (config.addons?.length) {
|
||||||
configDisplay.push(`${pc.blue("Addons:")} ${config.addons.join(", ")}`);
|
configDisplay.push(`${pc.blue("Addons:")} ${config.addons.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.runtime) {
|
||||||
|
flags.push(`--runtime ${config.runtime}`);
|
||||||
|
}
|
||||||
|
|
||||||
const baseCommand = "npx create-better-t-stack";
|
const baseCommand = "npx create-better-t-stack";
|
||||||
const projectName = config.projectName ? ` ${config.projectName}` : "";
|
const projectName = config.projectName ? ` ${config.projectName}` : "";
|
||||||
const flagString = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
const flagString = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
||||||
|
|||||||
@@ -3,15 +3,11 @@
|
|||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
|
||||||
"start": "node dist/src/index.js",
|
|
||||||
"build": "tsc && tsc-alias",
|
"build": "tsc && tsc-alias",
|
||||||
"dev:bun": "bun run --hot src/index.ts",
|
|
||||||
"check-types": "tsc --noEmit",
|
"check-types": "tsc --noEmit",
|
||||||
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.14.0",
|
|
||||||
"@hono/trpc-server": "^0.3.4",
|
"@hono/trpc-server": "^0.3.4",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
@@ -19,9 +15,7 @@
|
|||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tsc-alias": "^1.8.11",
|
"tsc-alias": "^1.8.11",
|
||||||
"tsx": "^4.19.2",
|
|
||||||
"@types/node": "^22.13.11",
|
|
||||||
"typescript": "^5.8.2"
|
"typescript": "^5.8.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { serve } from "@hono/node-server";
|
|
||||||
import { trpcServer } from "@hono/trpc-server";
|
import { trpcServer } from "@hono/trpc-server";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
@@ -12,30 +11,23 @@ const app = new Hono();
|
|||||||
app.use(logger());
|
app.use(logger());
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/*",
|
"/*",
|
||||||
cors({
|
cors({
|
||||||
origin: process.env.CORS_ORIGIN || "",
|
origin: process.env.CORS_ORIGIN || "",
|
||||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/trpc/*",
|
"/trpc/*",
|
||||||
trpcServer({
|
trpcServer({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: (_opts, hono) => {
|
createContext: (_opts, hono) => {
|
||||||
return createContext({ hono });
|
return createContext({ hono });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get("/", (c) => {
|
app.get("/", (c) => {
|
||||||
return c.text("OK");
|
return c.text("OK");
|
||||||
});
|
|
||||||
|
|
||||||
serve({
|
|
||||||
fetch: app.fetch,
|
|
||||||
port: 3000,
|
|
||||||
}, (info) => {
|
|
||||||
console.log(`Server is running on http://localhost:${info.port}`)
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ export default function SignInForm({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Sign in successful");
|
|
||||||
navigate({
|
navigate({
|
||||||
to: "/dashboard",
|
to: "/dashboard",
|
||||||
});
|
});
|
||||||
|
toast.success("Sign in successful");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.error.message);
|
toast.error(error.error.message);
|
||||||
|
|||||||
@@ -9,156 +9,156 @@ import { Input } from "./ui/input";
|
|||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
|
|
||||||
export default function SignUpForm({
|
export default function SignUpForm({
|
||||||
onSwitchToSignIn,
|
onSwitchToSignIn,
|
||||||
}: {
|
}: {
|
||||||
onSwitchToSignIn: () => void;
|
onSwitchToSignIn: () => void;
|
||||||
}) {
|
}) {
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: "/",
|
from: "/",
|
||||||
});
|
});
|
||||||
const { isPending } = authClient.useSession();
|
const { isPending } = authClient.useSession();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
name: "",
|
name: "",
|
||||||
},
|
},
|
||||||
onSubmit: async ({ value }) => {
|
onSubmit: async ({ value }) => {
|
||||||
await authClient.signUp.email(
|
await authClient.signUp.email(
|
||||||
{
|
{
|
||||||
email: value.email,
|
email: value.email,
|
||||||
password: value.password,
|
password: value.password,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Sign up successful");
|
navigate({
|
||||||
navigate({
|
to: "/dashboard",
|
||||||
to: "/dashboard",
|
});
|
||||||
});
|
toast.success("Sign up successful");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.error.message);
|
toast.error(error.error.message);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
onSubmit: z.object({
|
onSubmit: z.object({
|
||||||
name: z.string().min(2, "Name must be at least 2 characters"),
|
name: z.string().min(2, "Name must be at least 2 characters"),
|
||||||
email: z.string().email("Invalid email address"),
|
email: z.string().email("Invalid email address"),
|
||||||
password: z.string().min(6, "Password must be at least 6 characters"),
|
password: z.string().min(6, "Password must be at least 6 characters"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto mt-10 max-w-md p-6">
|
<div className="mx-auto mt-10 max-w-md p-6">
|
||||||
<h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
<h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
void form.handleSubmit();
|
void form.handleSubmit();
|
||||||
}}
|
}}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<form.Field name="name">
|
<form.Field name="name">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={field.name}>Name</Label>
|
<Label htmlFor={field.name}>Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id={field.name}
|
id={field.name}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
onChange={(e) => field.handleChange(e.target.value)}
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{field.state.meta.errors.map((error) => (
|
{field.state.meta.errors.map((error) => (
|
||||||
<p key={error?.message} className="text-red-500">
|
<p key={error?.message} className="text-red-500">
|
||||||
{error?.message}
|
{error?.message}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form.Field name="email">
|
<form.Field name="email">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={field.name}>Email</Label>
|
<Label htmlFor={field.name}>Email</Label>
|
||||||
<Input
|
<Input
|
||||||
id={field.name}
|
id={field.name}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
type="email"
|
type="email"
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
onChange={(e) => field.handleChange(e.target.value)}
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{field.state.meta.errors.map((error) => (
|
{field.state.meta.errors.map((error) => (
|
||||||
<p key={error?.message} className="text-red-500">
|
<p key={error?.message} className="text-red-500">
|
||||||
{error?.message}
|
{error?.message}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form.Field name="password">
|
<form.Field name="password">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor={field.name}>Password</Label>
|
<Label htmlFor={field.name}>Password</Label>
|
||||||
<Input
|
<Input
|
||||||
id={field.name}
|
id={field.name}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
type="password"
|
type="password"
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
onChange={(e) => field.handleChange(e.target.value)}
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
{field.state.meta.errors.map((error) => (
|
{field.state.meta.errors.map((error) => (
|
||||||
<p key={error?.message} className="text-red-500">
|
<p key={error?.message} className="text-red-500">
|
||||||
{error?.message}
|
{error?.message}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form.Subscribe>
|
<form.Subscribe>
|
||||||
{(state) => (
|
{(state) => (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
disabled={!state.canSubmit || state.isSubmitting}
|
disabled={!state.canSubmit || state.isSubmitting}
|
||||||
>
|
>
|
||||||
{state.isSubmitting ? "Submitting..." : "Sign Up"}
|
{state.isSubmitting ? "Submitting..." : "Sign Up"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</form.Subscribe>
|
</form.Subscribe>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
onClick={onSwitchToSignIn}
|
onClick={onSwitchToSignIn}
|
||||||
className="text-indigo-600 hover:text-indigo-800"
|
className="text-indigo-600 hover:text-indigo-800"
|
||||||
>
|
>
|
||||||
Already have an account? Sign In
|
Already have an account? Sign In
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { serve } from "@hono/node-server";
|
|
||||||
import { trpcServer } from "@hono/trpc-server";
|
import { trpcServer } from "@hono/trpc-server";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
@@ -13,34 +12,27 @@ const app = new Hono();
|
|||||||
app.use(logger());
|
app.use(logger());
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/*",
|
"/*",
|
||||||
cors({
|
cors({
|
||||||
origin: process.env.CORS_ORIGIN || "",
|
origin: process.env.CORS_ORIGIN || "",
|
||||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||||
allowHeaders: ["Content-Type", "Authorization"],
|
allowHeaders: ["Content-Type", "Authorization"],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
|
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
"/trpc/*",
|
"/trpc/*",
|
||||||
trpcServer({
|
trpcServer({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: (_opts, hono) => {
|
createContext: (_opts, hono) => {
|
||||||
return createContext({ hono });
|
return createContext({ hono });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get("/", (c) => {
|
app.get("/", (c) => {
|
||||||
return c.text("OK");
|
return c.text("OK");
|
||||||
});
|
|
||||||
|
|
||||||
serve({
|
|
||||||
fetch: app.fetch,
|
|
||||||
port: 3000,
|
|
||||||
}, (info) => {
|
|
||||||
console.log(`Server is running on http://localhost:${info.port}`)
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
|
import { protectedProcedure, publicProcedure, router } from "../lib/trpc";
|
||||||
import { router, publicProcedure, protectedProcedure } from "../lib/trpc";
|
|
||||||
import { todoRouter } from "./todo";
|
import { todoRouter } from "./todo";
|
||||||
|
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
healthCheck: publicProcedure.query(() => {
|
healthCheck: publicProcedure.query(() => {
|
||||||
return "OK";
|
return "OK";
|
||||||
}),
|
}),
|
||||||
privateData: protectedProcedure.query(({ ctx }) => {
|
privateData: protectedProcedure.query(({ ctx }) => {
|
||||||
return {
|
return {
|
||||||
message: "This is private",
|
message: "This is private",
|
||||||
user: ctx.session.user,
|
user: ctx.session.user,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
todo: todoRouter,
|
todo: todoRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|||||||
9989
package-lock.json
generated
Normal file
9989
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user