diff --git a/betterbase/apps/cli/src/index.ts b/betterbase/apps/cli/src/index.ts index d74eb82..32ee3c9 100644 --- a/betterbase/apps/cli/src/index.ts +++ b/betterbase/apps/cli/src/index.ts @@ -1,9 +1,9 @@ #!/usr/bin/env node /** - * Legacy bb wrapper entrypoint. + * Entrypoint for the legacy "bb" CLI that delegates to the canonical CLI implementation. * - * Forwards execution to the canonical CLI implementation in packages/cli. + * Dynamically imports the canonical CLI module and invokes it with the current process arguments. */ export async function runLegacyCli(): Promise { const cliModule = await import('../../../packages/cli/src/index'); @@ -12,4 +12,4 @@ export async function runLegacyCli(): Promise { if (import.meta.main) { await runLegacyCli(); -} +} \ No newline at end of file diff --git a/betterbase/packages/cli/src/build.ts b/betterbase/packages/cli/src/build.ts index 4e34db1..fbfecef 100644 --- a/betterbase/packages/cli/src/build.ts +++ b/betterbase/packages/cli/src/build.ts @@ -1,5 +1,10 @@ /** - * Build the CLI as a standalone bundled executable output. + * Builds the CLI into a standalone bundled executable and prefixes the output with a Bun shebang. + * + * If the build fails, this function throws an Error whose message includes the number of build logs. + * On success, it writes the bundled file to ./dist/index.js and prepends "#!/usr/bin/env bun" so the file can be executed directly. + * + * @throws Error When the Bun build reports failure; the error message contains the count of build logs. */ export async function buildStandaloneCli(): Promise { const result = await Bun.build({ @@ -21,4 +26,4 @@ export async function buildStandaloneCli(): Promise { await Bun.write(outputPath, `#!/usr/bin/env bun\n${compiled}`); } -await buildStandaloneCli(); +await buildStandaloneCli(); \ No newline at end of file diff --git a/betterbase/packages/cli/src/commands/init.ts b/betterbase/packages/cli/src/commands/init.ts index 6a856c8..97ce3e7 100644 --- a/betterbase/packages/cli/src/commands/init.ts +++ b/betterbase/packages/cli/src/commands/init.ts @@ -20,6 +20,12 @@ type DatabaseMode = z.infer; export type InitCommandOptions = z.infer; +/** + * Get a human-readable label for a chosen database mode. + * + * @param databaseMode - The database mode; one of 'local', 'neon', or 'turso' + * @returns The label: `'Neon (serverless Postgres)'` for `'neon'`, `'Turso (edge SQLite)'` for `'turso'`, or `'SQLite (local.db)'` for `'local'` and other values + */ function getDatabaseLabel(databaseMode: DatabaseMode): string { if (databaseMode === 'neon') { return 'Neon (serverless Postgres)'; @@ -32,6 +38,12 @@ function getDatabaseLabel(databaseMode: DatabaseMode): string { return 'SQLite (local.db)'; } +/** + * Installs project dependencies in the specified project directory using Bun. + * + * @param projectPath - File system path to the project directory where dependencies will be installed + * @throws Error if the install process exits with a non-zero code; message suggests running `bun install` manually + */ async function installDependencies(projectPath: string): Promise { const installProcess = Bun.spawn(['bun', 'install'], { cwd: projectPath, @@ -46,6 +58,13 @@ async function installDependencies(projectPath: string): Promise { } } +/** + * Initializes a Git repository inside the specified project directory. + * + * Attempts to run `git init` in the provided path and logs a warning if initialization fails. + * + * @param projectPath - Filesystem path of the project directory where the repository should be initialized + */ async function initializeGitRepository(projectPath: string): Promise { const gitProcess = Bun.spawn(['git', 'init'], { cwd: projectPath, @@ -60,6 +79,16 @@ async function initializeGitRepository(projectPath: string): Promise { } } +/** + * Build the contents of a package.json tailored to the chosen project settings. + * + * The generated package JSON includes the project name, `private: true`, `"type": "module"`, standard scripts for development and Drizzle, a `dependencies` object that varies by `databaseMode` and `useAuth`, and a set of `devDependencies`. + * + * @param projectName - The package `name` field to use in package.json + * @param databaseMode - The selected database mode; determines which database client dependency is included + * @param useAuth - Whether to include the authentication library dependency + * @returns A pretty-printed JSON string representing the package.json contents (with a trailing newline) + */ function buildPackageJson(projectName: string, databaseMode: DatabaseMode, useAuth: boolean): string { const dependencies: Record = { hono: '^4.11.9', @@ -103,6 +132,12 @@ function buildPackageJson(projectName: string, databaseMode: DatabaseMode, useAu return `${JSON.stringify(json, null, 2)}\n`; } +/** + * Builds the content of a drizzle.config.ts file configured for the given database mode. + * + * @param databaseMode - The selected database mode ('local', 'neon', or 'turso') used to choose the Drizzle dialect + * @returns The TypeScript source for a Drizzle configuration that sets schema path, output directory, chosen dialect, and `dbCredentials.url` (falling back to `file:local.db`) + */ function buildDrizzleConfig(databaseMode: DatabaseMode): string { const dialect: Record = { local: 'sqlite', @@ -123,6 +158,12 @@ export default defineConfig({ `; } +/** + * Produce the TypeScript source for a Drizzle ORM `users` schema tailored to the chosen database mode. + * + * @param databaseMode - The target database mode (`'local'`, `'neon'`, or `'turso'`) used to select the appropriate dialect and schema shape + * @returns A string containing the generated TypeScript schema file content for a `users` table compatible with the selected database dialect + */ function buildSchema(databaseMode: DatabaseMode): string { if (databaseMode === 'neon') { return `import { integer, pgTable, timestamp, varchar } from 'drizzle-orm/pg-core'; @@ -147,6 +188,12 @@ export const users = sqliteTable('users', { `; } +/** + * Generate the TypeScript source for a database index module configured for the specified database mode. + * + * @param databaseMode - The target database mode: `'local'` produces a Bun SQLite client, `'neon'` produces a node-postgres Pool with Drizzle, and `'turso'` produces a @libsql/client with Drizzle. + * @returns The source code string of a module that initializes and exports a configured `db` instance wired to the generated `schema`. + */ function buildDbIndex(databaseMode: DatabaseMode): string { if (databaseMode === 'neon') { return `import { drizzle } from 'drizzle-orm/node-postgres'; @@ -184,6 +231,11 @@ export const db = drizzle(client, { schema }); `; } +/** + * Generate TypeScript source for a minimal Hono authentication middleware placeholder. + * + * @returns A string containing TypeScript source that exports `authMiddleware` as a Hono middleware which currently forwards requests (`await next()`) and includes a `TODO` comment to implement session validation. + */ function buildAuthMiddleware(): string { return `import { createMiddleware } from 'hono/factory'; @@ -194,6 +246,11 @@ export const authMiddleware = createMiddleware(async (_c, next) => { `; } +/** + * Generate README.md content for a new project. + * + * @returns The README content string including the project title and recommended Bun scripts: `bun run dev`, `bun run db:generate`, and `bun run db:push`. + */ function buildReadme(projectName: string): string { return `# ${projectName} @@ -207,6 +264,14 @@ Generated with BetterBase CLI. `; } +/** + * Scaffold a new project at the given path by creating directories and writing generated configuration, source, and helper files according to the chosen database mode and authentication option. + * + * @param projectPath - Filesystem path where the project will be created + * @param projectName - Name used in the generated package.json and README + * @param databaseMode - Selected database mode used to tailor DB config, schema, and wiring (`local`, `neon`, or `turso`) + * @param useAuth - Include authentication middleware and related configuration when true + */ async function writeProjectFiles( projectPath: string, projectName: string, @@ -381,7 +446,9 @@ export default server; } /** - * Run the `bb init` command. + * Scaffolds a new BetterBase project by prompting for options, creating files, installing dependencies, and optionally initializing git. + * + * @param rawOptions - Partial CLI options used to pre-fill prompts (e.g., `projectName`) */ export async function runInitCommand(rawOptions: InitCommandOptions): Promise { const options = initOptionsSchema.parse(rawOptions); @@ -457,4 +524,4 @@ export async function runInitCommand(rawOptions: InitCommandOptions): Promise { const program = createProgram(); @@ -50,4 +54,4 @@ if (import.meta.main) { logger.error(message); process.exitCode = 1; } -} +} \ No newline at end of file diff --git a/betterbase/packages/cli/src/utils/logger.ts b/betterbase/packages/cli/src/utils/logger.ts index fe71756..c3ebfe7 100644 --- a/betterbase/packages/cli/src/utils/logger.ts +++ b/betterbase/packages/cli/src/utils/logger.ts @@ -1,29 +1,39 @@ import chalk from 'chalk'; /** - * Print an informational message to stdout. + * Print an informational message prefixed with an info icon and colored blue. + * + * The message is prefixed with "ℹ" and written to stdout. + * + * @param message - The text to print as an informational message */ export function info(message: string): void { console.log(chalk.blue(`ℹ ${message}`)); } /** - * Print a warning message to stdout. + * Logs a warning message to stdout with a yellow "⚠" prefix. + * + * @param message - The warning text to display */ export function warn(message: string): void { console.log(chalk.yellow(`⚠ ${message}`)); } /** - * Print an error message to stderr. + * Print an error message to stderr prefixed with a red "✖" icon. + * + * @param message - The error message to print */ export function error(message: string): void { console.error(chalk.red(`✖ ${message}`)); } /** - * Print a success message to stdout. + * Print a success message to stdout prefixed with a check mark and colored green. + * + * @param message - The message text to display after the check mark */ export function success(message: string): void { console.log(chalk.green(`✔ ${message}`)); -} +} \ No newline at end of file diff --git a/betterbase/packages/cli/src/utils/prompts.ts b/betterbase/packages/cli/src/utils/prompts.ts index 591236f..b12987f 100644 --- a/betterbase/packages/cli/src/utils/prompts.ts +++ b/betterbase/packages/cli/src/utils/prompts.ts @@ -23,7 +23,11 @@ const selectOptionsSchema = z.object({ }); /** - * Prompt for text input. + * Prompts the user to enter text using the provided message. + * + * @param options.message - The prompt message shown to the user + * @param options.initial - Optional default value prefilled in the input + * @returns The text entered by the user */ export async function text(options: { message: string; initial?: string }): Promise { const parsed = textOptionsSchema.parse(options); @@ -41,7 +45,11 @@ export async function text(options: { message: string; initial?: string }): Prom } /** - * Prompt for yes/no confirmation. + * Prompt the user with a yes/no confirmation. + * + * @param options.message - The message to display to the user + * @param options.initial - The default selection if the user just presses Enter + * @returns `true` if the user confirms, `false` otherwise. */ export async function confirm(options: { message: string; initial?: boolean }): Promise { const parsed = confirmOptionsSchema.parse(options); @@ -59,7 +67,13 @@ export async function confirm(options: { message: string; initial?: boolean }): } /** - * Prompt for selecting one option. + * Prompt the user to choose one option from a list. + * + * @param options - Configuration for the prompt: + * - `message`: The text displayed to the user. + * - `choices`: Array of choices where each item has a `name` (label shown) and `value` (returned value). + * - `initial`: Optional `value` to select by default. + * @returns The `value` of the selected choice */ export async function select( options: { message: string; choices: Array<{ name: string; value: string }>; initial?: string }, @@ -77,4 +91,4 @@ export async function select( ]); return response.value; -} +} \ No newline at end of file diff --git a/betterbase/templates/base/src/middleware/validation.ts b/betterbase/templates/base/src/middleware/validation.ts index 2330e7c..420bf1b 100644 --- a/betterbase/templates/base/src/middleware/validation.ts +++ b/betterbase/templates/base/src/middleware/validation.ts @@ -2,6 +2,14 @@ import { HTTPException } from 'hono/http-exception'; import type { ZodType } from 'zod'; import { z } from 'zod'; +/** + * Validate and parse an input value against a Zod schema. + * + * @param schema - Zod schema to validate and parse the input into type `T` + * @param body - The value to validate + * @returns The validated and parsed value as type `T` + * @throws HTTPException with status 400 when validation fails. The exception payload contains `message: "Validation failed"` and `cause.errors`, an array of objects each with `path` (dot-joined string), `message`, and `code` + */ export function parseBody(schema: ZodType, body: unknown): T { const result = schema.safeParse(body); @@ -25,4 +33,4 @@ export function parseBody(schema: ZodType, body: unknown): T { export const createUserSchema = z.object({ email: z.string().email(), name: z.string().min(1), -}); +}); \ No newline at end of file