create-fluxstack
Version:
ā” Revolutionary full-stack TypeScript framework with Declarative Config System, Elysia + React + Bun
605 lines (495 loc) ⢠14.6 kB
text/typescript
import { spawn } from "bun"
import { join, resolve } from "path"
import { mkdir } from "fs/promises"
export interface CreateProjectOptions {
name: string
targetDir?: string
template?: 'basic' | 'full'
}
export class ProjectCreator {
private projectName: string
private targetDir: string
constructor(options: CreateProjectOptions) {
this.projectName = options.name
this.targetDir = options.targetDir || resolve(process.cwd(), options.name)
// Template option available but basic template is used for now
// const template = options.template || 'basic'
}
async create() {
console.log(`š Creating FluxStack project: ${this.projectName}`)
console.log(`š Target directory: ${this.targetDir}`)
console.log()
try {
// 1. Create project directory
await this.createDirectory()
// 2. Copy template files
await this.copyTemplate()
// 3. Generate package.json
await this.generatePackageJson()
// 4. Generate config files (including .gitignore)
await this.generateConfigFiles()
// 5. Initialize git (before installing dependencies)
await this.initGit()
// 6. Install dependencies (last step)
await this.installDependencies()
console.log()
console.log("š Project created successfully!")
console.log()
console.log("Next steps:")
console.log(` cd ${this.projectName}`)
console.log(` bun run dev`)
console.log()
console.log("Happy coding! š")
} catch (error) {
console.error("ā Error creating project:", error instanceof Error ? error.message : String(error))
process.exit(1)
}
}
private async createDirectory() {
console.log("š Creating project directory...")
await mkdir(this.targetDir, { recursive: true })
}
private async copyTemplate() {
console.log("š Copying template files...")
// Copy files using Bun's built-in functions for better performance
const rootDir = join(__dirname, '..', '..')
// Copy app structure (exclude node_modules and dist)
await this.copyDirectory(
join(rootDir, 'app'),
join(this.targetDir, 'app'),
['node_modules', 'dist', '.vite']
)
// Copy core framework (exclude node_modules)
await this.copyDirectory(
join(rootDir, 'core'),
join(this.targetDir, 'core'),
['node_modules']
)
// Copy config
await this.copyDirectory(
join(rootDir, 'config'),
join(this.targetDir, 'config')
)
// Copy plugins
await this.copyDirectory(
join(rootDir, 'plugins'),
join(this.targetDir, 'plugins')
)
}
private async copyDirectory(src: string, dest: string, exclude: string[] = []) {
await mkdir(dest, { recursive: true })
const fs = await import("fs/promises")
let entries: any[] = []
try {
entries = await fs.readdir(src, { withFileTypes: true })
} catch (error) {
console.warn(`Warning: Could not read directory ${src}`)
return
}
for (const entry of entries) {
if (exclude.includes(entry.name)) continue
const srcPath = join(src, entry.name)
const destPath = join(dest, entry.name)
if (entry.isDirectory()) {
await this.copyDirectory(srcPath, destPath, exclude)
} else {
const content = await Bun.file(srcPath).text()
await Bun.write(destPath, content)
}
}
}
private async generatePackageJson() {
console.log("š¦ Generating package.json...")
const packageJson = {
name: this.projectName,
version: "1.0.0",
description: `FluxStack project: ${this.projectName}`,
keywords: ["fluxstack", "full-stack", "typescript", "elysia", "react", "bun"],
author: "FluxStack Developer",
license: "MIT",
module: "app/server/index.ts",
type: "module",
bin: {
flux: "./core/cli/index.ts"
},
scripts: {
dev: "bun run core/cli/index.ts dev",
"dev:frontend": "bun run core/cli/index.ts frontend",
"dev:backend": "bun run core/cli/index.ts backend",
"sync-version": "bun run core/utils/sync-version.ts",
build: "bun run core/cli/index.ts build",
"build:frontend": "bun run core/cli/index.ts build:frontend",
"build:backend": "bun run core/cli/index.ts build:backend",
start: "bun run core/cli/index.ts start",
test: "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest --watch"
},
devDependencies: {
"@types/bun": "latest",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1",
"@testing-library/react": "^14.0.0",
"@testing-library/jest-dom": "^6.1.0",
"@testing-library/user-event": "^14.5.0",
"@vitest/ui": "^1.0.0",
"@vitest/coverage-v8": "^1.0.0",
"jsdom": "^23.0.0",
typescript: "^5.0.0",
vitest: "^1.0.0"
},
dependencies: {
"@elysiajs/eden": "^1.3.2",
"@sinclair/typebox": "^0.34.41",
"@vitejs/plugin-react": "^4.0.0",
"chalk": "^5.3.0",
"chokidar": "^4.0.3",
"elysia": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.5.0",
"uuid": "^13.0.0",
"vite": "^5.0.0",
"ws": "^8.18.3",
"zustand": "^5.0.8"
}
}
await Bun.write(
join(this.targetDir, "package.json"),
JSON.stringify(packageJson, null, 2)
)
}
private async generateConfigFiles() {
console.log("āļø Generating config files...")
// TypeScript config
const tsConfig = {
compilerOptions: {
lib: ["ESNext", "DOM"],
target: "ESNext",
module: "ESNext",
moduleDetection: "force",
jsx: "react-jsx",
allowJs: true,
moduleResolution: "bundler",
allowImportingTsExtensions: true,
verbatimModuleSyntax: true,
noEmit: true,
baseUrl: ".",
paths: {
"@/*": ["./*"],
"@/core/*": ["./core/*"],
"@/app/*": ["./app/*"],
"@/config/*": ["./config/*"],
"@/shared/*": ["./app/shared/*"]
},
strict: true,
skipLibCheck: true,
noFallthroughCasesInSwitch: true,
noUnusedLocals: false,
noUnusedParameters: false,
noPropertyAccessFromIndexSignature: false
}
}
await Bun.write(
join(this.targetDir, "tsconfig.json"),
JSON.stringify(tsConfig, null, 2)
)
// Bun config
const bunConfig = `
[]
target = "bun"
[]
cache = true
lockfile = true
[]
"@" = "."
"@/core" = "./core"
"@/app" = "./app"
"@/config" = "./config"
"@/shared" = "./app/shared"
`
await Bun.write(join(this.targetDir, "bunfig.toml"), bunConfig)
// Vite config
const viteConfig = `import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, '.'),
'@/core': resolve(__dirname, './core'),
'@/app': resolve(__dirname, './app'),
'@/config': resolve(__dirname, './config'),
'@/shared': resolve(__dirname, './app/shared')
}
},
server: {
port: 5173,
host: 'localhost',
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
},
build: {
outDir: 'dist/client',
emptyOutDir: true,
sourcemap: true
}
})
`
await Bun.write(join(this.targetDir, "vite.config.ts"), viteConfig)
// Get FluxStack version dynamically
const { FLUXSTACK_VERSION } = await import("../utils/version")
// Environment file
const envContent = `
NODE_ENV=development
PORT=3000
HOST=localhost
FRONTEND_PORT=5173
VITE_API_URL=http://localhost:3000
VITE_APP_NAME=FluxStack
VITE_APP_VERSION=${FLUXSTACK_VERSION}
VITE_NODE_ENV=development
FLUXSTACK_APP_VERSION=${FLUXSTACK_VERSION}
BACKEND_PORT=3001
API_URL=http://localhost:3001
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
CORS_METHODS=GET,POST,PUT,DELETE,OPTIONS
CORS_HEADERS=Content-Type,Authorization
LOG_LEVEL=info
BUILD_TARGET=bun
BUILD_OUTDIR=dist
`
await Bun.write(join(this.targetDir, ".env"), envContent)
// .gitignore
const gitignoreContent = `
node_modules/
.pnp
.pnp.js
/dist
/build
/.next/
/out/
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
pids
*.pid
*.seed
*.pid.lock
coverage/
*.lcov
.nyc_output
jspm_packages/
*.tsbuildinfo
.npm
.eslintcache
.stylelintcache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.node_repl_history
*.tgz
.yarn-integrity
.cache
.parcel-cache
.next
.nuxt
dist
.out
.storybook-out
tmp/
temp/
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# FluxStack specific
uploads/
public/uploads/
.fluxstack/
# Bun
bun.lockb
`
await Bun.write(join(this.targetDir, ".gitignore"), gitignoreContent)
// README
const readme = `# ${this.projectName}
Modern full-stack TypeScript application built with FluxStack framework.
## Tech Stack
- **Backend**: Elysia.js (high-performance web framework)
- **Frontend**: React + Vite (modern development experience)
- **Runtime**: Bun (ultra-fast JavaScript runtime)
- **Type Safety**: Eden Treaty (end-to-end type safety)
## Getting Started
### Install Dependencies
\`\`\`bash
bun install
\`\`\`
### Development
#### Full-Stack (Recommended)
\`\`\`bash
bun run dev
# Frontend + Backend integrated at http://localhost:3000
\`\`\`
#### Separate Development
\`\`\`bash
# Terminal 1: Backend API
bun run dev:backend
# API at http://localhost:3001
# Terminal 2: Frontend
bun run dev:frontend
# Frontend at http://localhost:5173
\`\`\`
### Production
\`\`\`bash
# Build everything
bun run build
# Start production server
bun run start
\`\`\`
## Project Structure
\`\`\`
${this.projectName}/
āāā app/ # Your application code
ā āāā server/ # Backend (controllers, routes)
ā āāā client/ # Frontend (React components)
ā āāā shared/ # Shared types
āāā core/ # FluxStack framework (don't edit)
āāā config/ # Configuration files
āāā dist/ # Production build
\`\`\`
## Available Commands
- \`bun run dev\` - Full-stack development
- \`bun run dev:frontend\` - Frontend only
- \`bun run dev:backend\` - Backend only
- \`bun run build\` - Build for production
- \`bun run start\` - Start production server
## Health Check
\`\`\`bash
curl http://localhost:3000/api/health
\`\`\`
Built with ā¤ļø using FluxStack framework.
`
await Bun.write(join(this.targetDir, "README.md"), readme)
}
private async installDependencies() {
console.log("š¦ Installing dependencies...")
const installProcess = spawn({
cmd: ["bun", "install"],
cwd: this.targetDir,
stdout: "pipe",
stderr: "pipe"
})
const exitCode = await installProcess.exited
if (exitCode !== 0) {
throw new Error("Failed to install dependencies")
}
}
private async initGit() {
console.log("š§ Initializing git repository...")
try {
// Initialize git repository
await spawn({
cmd: ["git", "init", "--quiet"],
cwd: this.targetDir,
stdout: "ignore",
stderr: "ignore"
}).exited
// Add all files
await spawn({
cmd: ["git", "add", "."],
cwd: this.targetDir,
stdout: "ignore",
stderr: "ignore"
}).exited
// Initial commit
await spawn({
cmd: ["git", "commit", "-m", "Initial commit - FluxStack project created", "--quiet"],
cwd: this.targetDir,
stdout: "ignore",
stderr: "ignore"
}).exited
} catch (error) {
console.warn("ā ļø Git initialization failed (git may not be installed)")
}
}
}