Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Refactored codebase for better maintainability and organization
- Extracted `copyRecursive` and validation logic into `src/utils.ts`
- Extracted git initialization logic into `src/git.ts`
- Extracted template setup logic into `src/template.ts`
- Centralized constants and configuration into `src/constants.ts`
- Reduced main function complexity from 296 to 60 lines
- Added JSDoc comments to all exported functions
- Improved code reusability and testability

## [0.1.2] - 2026-01-03

### Added
Expand Down
63 changes: 36 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
# create-ely

Scaffold ElysiaJS projects with ease using [Bun](https://bun.sh).
[![Lint](https://github.com/truehazker/create-ely/actions/workflows/lint.yml/badge.svg)](https://github.com/truehazker/create-ely/actions/workflows/lint.yml)
[![npm version](https://img.shields.io/npm/v/create-ely.svg)](https://www.npmjs.com/package/create-ely)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Usage
[![Bun](https://img.shields.io/badge/Bun-000000?logo=bun)](https://bun.sh)
[![ElysiaJS](https://img.shields.io/badge/ElysiaJS-6366f1?logo=elysia&logoColor=white)](https://elysiajs.com)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?logo=postgresql&logoColor=white)](https://www.postgresql.org/)

Create a new ElysiaJS project:
The fastest way to scaffold production-ready [ElysiaJS](https://elysiajs.com) projects with [Bun](https://bun.sh).

![Demo](https://github.com/user-attachments/assets/67386464-eb39-4b71-8b9b-34039e2861d0)

## Quick Start

Create a new project:

```bash
bunx create-ely my-project
bun create ely
```

You'll be prompted to choose between:
Or with a project name:

- **Backend only** - ElysiaJS API with PostgreSQL, Drizzle ORM
- **Monorepo** - Backend + Frontend (React with TanStack Router)
```bash
bun create ely my-project
```

## Templates
You'll be prompted to choose:

### Backend Only
- **Backend Only** - API-first ElysiaJS backend with PostgreSQL, Drizzle ORM, and OpenAPI docs
- **Monorepo** - Full-stack setup with React frontend, TanStack Router, and shared workspace

A production-ready ElysiaJS backend with:
## What's Included

- PostgreSQL database with Drizzle ORM
- Type-safe API with OpenAPI documentation
- Global error handling
- Structured logging with Pino
- Docker support
- Environment validation
**Backend Template:**

### Monorepo
- PostgreSQL + Drizzle ORM for type-safe database access
- OpenAPI documentation out of the box
- Global error handling and structured logging (Pino)
- Docker support for development and production
- Environment validation with type safety

Full-stack setup with:
**Monorepo Template:**

- Backend: Everything from Backend Only template
- Frontend: React + TanStack Router + Vite
- Workspace configuration with Bun
- Everything from Backend template
- React frontend with TanStack Router and Vite
- Bun workspaces for seamless monorepo management

## Development
## Contributing

To test the CLI locally:
> **⚠️ Important:** This project uses Git submodules for templates. Make sure to clone with `git clone --recurse-submodules` or run `git submodule update --init --recursive` after cloning.

```bash
bun link
bunx create-ely
```
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.

## License

Expand Down
Binary file added assets/demo.mp4
Binary file not shown.
20 changes: 20 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const TEMPLATE_TYPES = {
BACKEND: 'backend',
MONOREPO: 'monorepo',
} as const;

export const DEFAULT_PROJECT_NAME = 'my-ely-app';

export const PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;

export const EXCLUDED_COPY_PATTERNS = ['node_modules', '.git'];

export const PORTS = {
BACKEND: 3000,
FRONTEND: 5173,
} as const;

export const TEMPLATE_PATHS = {
BACKEND_BIOME_TEMPLATE: 'apps/backend-biome.json.template',
BACKEND_BIOME_TARGET: 'apps/backend/biome.json',
} as const;
95 changes: 95 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as clack from '@clack/prompts';

/**
* Initializes a git repository in the target directory and creates an initial commit
* @param targetDir - The directory to initialize git in
* @returns Promise that resolves when git initialization is complete
*/
export async function initializeGit(targetDir: string): Promise<void> {
const initGit = await clack.confirm({
message: 'Initialize git repository?',
initialValue: true,
});

if (clack.isCancel(initGit)) {
clack.cancel('Operation cancelled');
process.exit(0);
}

if (!initGit) {
return;
}

// Check if git is available
const gitCheckProc = Bun.spawn(['git', '--version'], {
stdout: 'pipe',
stderr: 'pipe',
});
await gitCheckProc.exited;

if (gitCheckProc.exitCode !== 0) {
clack.log.warn(
'Git is not installed or not available. Skipping git initialization.',
);
return;
}

const gitSpinner = clack.spinner();
gitSpinner.start('Initializing git repository...');

try {
const gitInitProc = Bun.spawn(['git', 'init'], {
cwd: targetDir,
stdout: 'pipe',
stderr: 'pipe',
});
await gitInitProc.exited;

if (gitInitProc.exitCode !== 0) {
gitSpinner.stop('Failed to initialize git');
clack.log.warn(
'Git initialization failed. You can initialize manually later.',
);
return;
}

gitSpinner.stop('Git repository initialized');

// Make initial commit
gitSpinner.start('Creating initial commit...');

const gitAddProc = Bun.spawn(['git', 'add', '.'], {
cwd: targetDir,
stdout: 'pipe',
stderr: 'pipe',
});
await gitAddProc.exited;

if (gitAddProc.exitCode !== 0) {
gitSpinner.stop('Git initialized (add failed)');
clack.log.warn('Failed to stage files. You can add them manually later.');
return;
}

const gitCommitProc = Bun.spawn(['git', 'commit', '-m', 'Initial commit'], {
cwd: targetDir,
stdout: 'pipe',
stderr: 'pipe',
});
await gitCommitProc.exited;

if (gitCommitProc.exitCode === 0) {
gitSpinner.stop('Initial commit created');
} else {
gitSpinner.stop('Git initialized (commit failed)');
clack.log.warn(
'Failed to create initial commit. You can commit manually later.',
);
}
} catch {
gitSpinner.stop('Git initialization failed');
clack.log.warn(
'An error occurred during git initialization. You can initialize manually later.',
);
}
}
Loading