bunkit-cli 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1297 -141
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23958,7 +23958,17 @@ var ProjectConfigSchema = exports_external.object({
|
|
|
23958
23958
|
path: exports_external.string(),
|
|
23959
23959
|
features: exports_external.array(exports_external.string()).optional(),
|
|
23960
23960
|
git: exports_external.boolean().default(true),
|
|
23961
|
-
install: exports_external.boolean().default(true)
|
|
23961
|
+
install: exports_external.boolean().default(true),
|
|
23962
|
+
database: exports_external.enum(["postgres-drizzle", "supabase", "sqlite-drizzle", "none"]).optional(),
|
|
23963
|
+
codeQuality: exports_external.enum(["ultracite", "biome"]).default("ultracite"),
|
|
23964
|
+
tsStrictness: exports_external.enum(["strict", "moderate", "loose"]).default("strict"),
|
|
23965
|
+
uiLibrary: exports_external.enum(["shadcn", "none"]).optional(),
|
|
23966
|
+
cssFramework: exports_external.enum(["tailwind", "vanilla", "css-modules"]).optional(),
|
|
23967
|
+
testing: exports_external.enum(["bun-test", "vitest", "none"]).default("bun-test"),
|
|
23968
|
+
docker: exports_external.boolean().default(false),
|
|
23969
|
+
cicd: exports_external.boolean().default(false),
|
|
23970
|
+
envExample: exports_external.boolean().default(true),
|
|
23971
|
+
pathAliases: exports_external.boolean().default(true)
|
|
23962
23972
|
});
|
|
23963
23973
|
var FeatureConfigSchema = exports_external.object({
|
|
23964
23974
|
name: exports_external.enum(["auth", "database", "ui", "payments", "email", "storage"]),
|
|
@@ -24094,7 +24104,17 @@ function createTemplateContext(config) {
|
|
|
24094
24104
|
description: `Project created with bunkit`,
|
|
24095
24105
|
license: "MIT",
|
|
24096
24106
|
features: config.features || [],
|
|
24097
|
-
supportsTypeScript: true
|
|
24107
|
+
supportsTypeScript: true,
|
|
24108
|
+
database: config.database,
|
|
24109
|
+
codeQuality: config.codeQuality,
|
|
24110
|
+
tsStrictness: config.tsStrictness,
|
|
24111
|
+
uiLibrary: config.uiLibrary,
|
|
24112
|
+
cssFramework: config.cssFramework,
|
|
24113
|
+
testing: config.testing,
|
|
24114
|
+
docker: config.docker,
|
|
24115
|
+
cicd: config.cicd,
|
|
24116
|
+
envExample: config.envExample,
|
|
24117
|
+
pathAliases: config.pathAliases
|
|
24098
24118
|
};
|
|
24099
24119
|
}
|
|
24100
24120
|
// ../core/src/install.ts
|
|
@@ -24112,7 +24132,7 @@ async function installDependencies(cwd, packages) {
|
|
|
24112
24132
|
throw error;
|
|
24113
24133
|
}
|
|
24114
24134
|
}
|
|
24115
|
-
// src/commands/init.
|
|
24135
|
+
// src/commands/init.enhanced.ts
|
|
24116
24136
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
24117
24137
|
|
|
24118
24138
|
// ../templates/src/render.ts
|
|
@@ -24161,6 +24181,551 @@ coverage = true
|
|
|
24161
24181
|
`;
|
|
24162
24182
|
await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
|
|
24163
24183
|
}
|
|
24184
|
+
// ../templates/src/generators/ultracite.ts
|
|
24185
|
+
async function setupUltracite(projectPath, context) {
|
|
24186
|
+
const biomeConfig = `{
|
|
24187
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
24188
|
+
"extends": [
|
|
24189
|
+
"ultracite/core",
|
|
24190
|
+
${context.cssFramework === "tailwind" ? `"ultracite/react",
|
|
24191
|
+
"ultracite/next"` : '"ultracite/react"'}
|
|
24192
|
+
],
|
|
24193
|
+
"vcs": {
|
|
24194
|
+
"enabled": true,
|
|
24195
|
+
"clientKind": "git",
|
|
24196
|
+
"useIgnoreFile": true
|
|
24197
|
+
},
|
|
24198
|
+
"files": {
|
|
24199
|
+
"ignore": [
|
|
24200
|
+
"node_modules",
|
|
24201
|
+
"dist",
|
|
24202
|
+
"build",
|
|
24203
|
+
".next",
|
|
24204
|
+
".turbo",
|
|
24205
|
+
"coverage"
|
|
24206
|
+
]
|
|
24207
|
+
},
|
|
24208
|
+
"formatter": {
|
|
24209
|
+
"enabled": true,
|
|
24210
|
+
"formatWithErrors": false,
|
|
24211
|
+
"indentStyle": "space",
|
|
24212
|
+
"indentWidth": 2,
|
|
24213
|
+
"lineWidth": 100,
|
|
24214
|
+
"lineEnding": "lf"
|
|
24215
|
+
},
|
|
24216
|
+
"linter": {
|
|
24217
|
+
"enabled": true,
|
|
24218
|
+
"rules": {
|
|
24219
|
+
"recommended": true
|
|
24220
|
+
}
|
|
24221
|
+
},
|
|
24222
|
+
"javascript": {
|
|
24223
|
+
"formatter": {
|
|
24224
|
+
"quoteStyle": "single",
|
|
24225
|
+
"trailingCommas": "es5",
|
|
24226
|
+
"semicolons": "always",
|
|
24227
|
+
"arrowParentheses": "always",
|
|
24228
|
+
"bracketSpacing": true,
|
|
24229
|
+
"jsxQuoteStyle": "double",
|
|
24230
|
+
"quoteProperties": "asNeeded"
|
|
24231
|
+
}
|
|
24232
|
+
},
|
|
24233
|
+
"json": {
|
|
24234
|
+
"formatter": {
|
|
24235
|
+
"enabled": true,
|
|
24236
|
+
"indentWidth": 2
|
|
24237
|
+
}
|
|
24238
|
+
}
|
|
24239
|
+
}
|
|
24240
|
+
`;
|
|
24241
|
+
await writeFile(join(projectPath, "biome.jsonc"), biomeConfig);
|
|
24242
|
+
const cursorRules = `# ${context.projectName} - Cursor AI Rules
|
|
24243
|
+
|
|
24244
|
+
## Code Style (Ultracite)
|
|
24245
|
+
- Follow Biome configuration in biome.jsonc
|
|
24246
|
+
- Use single quotes for JavaScript/TypeScript
|
|
24247
|
+
- Use double quotes for JSX attributes
|
|
24248
|
+
- Always use semicolons
|
|
24249
|
+
- 2-space indentation
|
|
24250
|
+
- 100 character line width
|
|
24251
|
+
- ES5 trailing commas
|
|
24252
|
+
|
|
24253
|
+
## TypeScript
|
|
24254
|
+
- Strict mode: ${context.tsStrictness === "strict" ? "enabled" : context.tsStrictness === "moderate" ? "moderate" : "disabled"}
|
|
24255
|
+
- Always define types explicitly for function parameters and return values
|
|
24256
|
+
- Use \`type\` for object types, \`interface\` for contracts
|
|
24257
|
+
- Prefer \`const\` assertions for literal types
|
|
24258
|
+
|
|
24259
|
+
## React/Next.js Best Practices
|
|
24260
|
+
- Use Server Components by default (Next.js 16)
|
|
24261
|
+
- Add 'use client' only when needed (hooks, browser APIs, interactivity)
|
|
24262
|
+
- Always await \`params\` and \`searchParams\` in Next.js 16
|
|
24263
|
+
- Use descriptive variable names (no \`c\`, \`ctx\`, \`req\`, \`res\`)
|
|
24264
|
+
|
|
24265
|
+
${context.cssFramework === "tailwind" ? `## Tailwind CSS
|
|
24266
|
+
- Use utility classes for styling
|
|
24267
|
+
- Follow mobile-first responsive design
|
|
24268
|
+
- Use OKLCH colors from theme when available
|
|
24269
|
+
- Avoid arbitrary values unless necessary` : ""}
|
|
24270
|
+
|
|
24271
|
+
${context.database && context.database !== "none" ? `## Database (${context.database})
|
|
24272
|
+
- Always use Drizzle ORM for type-safe queries
|
|
24273
|
+
- Define schema in separate files by domain
|
|
24274
|
+
- Use transactions for multi-step operations
|
|
24275
|
+
- Always handle database errors gracefully` : ""}
|
|
24276
|
+
|
|
24277
|
+
## File Naming
|
|
24278
|
+
- Pages: \`page.tsx\`, \`layout.tsx\`
|
|
24279
|
+
- Components: PascalCase (\`UserCard.tsx\`)
|
|
24280
|
+
- Utilities: camelCase (\`formatDate.ts\`)
|
|
24281
|
+
- Types: \`*.types.ts\` or in \`types/\` directory
|
|
24282
|
+
|
|
24283
|
+
## Testing
|
|
24284
|
+
- Write tests for critical business logic
|
|
24285
|
+
- Use Bun's built-in test runner
|
|
24286
|
+
- Follow AAA pattern: Arrange, Act, Assert
|
|
24287
|
+
- Mock external dependencies
|
|
24288
|
+
|
|
24289
|
+
## AI Code Generation Guidelines
|
|
24290
|
+
- Always generate complete, working code
|
|
24291
|
+
- Include error handling
|
|
24292
|
+
- Add TypeScript types
|
|
24293
|
+
- Write self-documenting code with clear names
|
|
24294
|
+
- Add comments only for complex logic
|
|
24295
|
+
- Follow the project's existing patterns
|
|
24296
|
+
`;
|
|
24297
|
+
await writeFile(join(projectPath, ".cursorrules"), cursorRules);
|
|
24298
|
+
const windsurfRules = `# ${context.projectName} - Windsurf AI Rules
|
|
24299
|
+
|
|
24300
|
+
This project uses **Ultracite** for code quality - an AI-optimized Biome preset.
|
|
24301
|
+
|
|
24302
|
+
## Code Quality Rules
|
|
24303
|
+
- Lint: \`bun run lint\`
|
|
24304
|
+
- Format: \`bun run format\`
|
|
24305
|
+
- Config: See \`biome.jsonc\`
|
|
24306
|
+
|
|
24307
|
+
## Key Conventions
|
|
24308
|
+
1. **TypeScript**: ${context.tsStrictness} mode
|
|
24309
|
+
2. **Quotes**: Single quotes (JS/TS), double quotes (JSX)
|
|
24310
|
+
3. **Semicolons**: Always required
|
|
24311
|
+
4. **Indentation**: 2 spaces
|
|
24312
|
+
5. **Line width**: 100 characters
|
|
24313
|
+
|
|
24314
|
+
## Framework-Specific
|
|
24315
|
+
${context.cssFramework === "tailwind" ? `- **Tailwind CSS**: Utility-first styling
|
|
24316
|
+
` : ""}${context.database && context.database !== "none" ? `- **Database**: ${context.database} with Drizzle ORM
|
|
24317
|
+
` : ""}- **Testing**: ${context.testing}
|
|
24318
|
+
|
|
24319
|
+
## Variable Naming (CRITICAL)
|
|
24320
|
+
\u274C NEVER use: \`c\`, \`ctx\`, \`e\`, \`req\`, \`res\`, \`data\`, \`temp\`
|
|
24321
|
+
\u2705 ALWAYS use: \`context\`, \`error\`, \`request\`, \`response\`, \`userData\`, \`temporaryBuffer\`
|
|
24322
|
+
|
|
24323
|
+
## React/Next.js 16
|
|
24324
|
+
- Server Components by default
|
|
24325
|
+
- \`'use client'\` only when necessary
|
|
24326
|
+
- Always \`await params\` and \`await searchParams\`
|
|
24327
|
+
|
|
24328
|
+
Sync with \`.cursorrules\` for full AI guidelines.
|
|
24329
|
+
`;
|
|
24330
|
+
await writeFile(join(projectPath, ".windsurfrules"), windsurfRules);
|
|
24331
|
+
const claudeMd = `# ${context.projectName}
|
|
24332
|
+
|
|
24333
|
+
AI-assisted development with Ultracite code quality.
|
|
24334
|
+
|
|
24335
|
+
## Quick Start
|
|
24336
|
+
|
|
24337
|
+
\`\`\`bash
|
|
24338
|
+
bun install
|
|
24339
|
+
bun dev
|
|
24340
|
+
\`\`\`
|
|
24341
|
+
|
|
24342
|
+
## Code Quality
|
|
24343
|
+
|
|
24344
|
+
This project uses **Ultracite** - an AI-optimized Biome preset that keeps code consistent across:
|
|
24345
|
+
- Cursor
|
|
24346
|
+
- Windsurf
|
|
24347
|
+
- Claude Code
|
|
24348
|
+
- Zed
|
|
24349
|
+
|
|
24350
|
+
### Commands
|
|
24351
|
+
|
|
24352
|
+
\`\`\`bash
|
|
24353
|
+
bun run lint # Check code quality
|
|
24354
|
+
bun run format # Auto-fix issues
|
|
24355
|
+
\`\`\`
|
|
24356
|
+
|
|
24357
|
+
## AI Development Guidelines
|
|
24358
|
+
|
|
24359
|
+
See \`.cursorrules\` and \`.windsurfrules\` for comprehensive AI coding guidelines.
|
|
24360
|
+
|
|
24361
|
+
### Critical Rules
|
|
24362
|
+
|
|
24363
|
+
1. **Descriptive Names**: No \`c\`, \`ctx\`, \`e\` - use \`context\`, \`error\`, etc.
|
|
24364
|
+
2. **TypeScript**: ${context.tsStrictness} mode
|
|
24365
|
+
3. **Server Components**: Default in Next.js 16
|
|
24366
|
+
4. **Error Handling**: Always handle errors gracefully
|
|
24367
|
+
|
|
24368
|
+
## Tech Stack
|
|
24369
|
+
|
|
24370
|
+
- **Runtime**: Bun
|
|
24371
|
+
- **Framework**: ${context.preset === "web" || context.preset === "full" ? "Next.js 16 + React 19" : context.preset === "api" ? "Hono" : "Minimal"}
|
|
24372
|
+
${context.cssFramework === "tailwind" ? `- **Styling**: Tailwind CSS 4
|
|
24373
|
+
` : ""}${context.database && context.database !== "none" ? `- **Database**: ${context.database}
|
|
24374
|
+
` : ""}${context.uiLibrary === "shadcn" ? `- **UI**: shadcn/ui
|
|
24375
|
+
` : ""}- **Code Quality**: Ultracite (Biome)
|
|
24376
|
+
- **Testing**: ${context.testing}
|
|
24377
|
+
`;
|
|
24378
|
+
await writeFile(join(projectPath, "CLAUDE.md"), claudeMd);
|
|
24379
|
+
}
|
|
24380
|
+
async function setupBiome(projectPath, context) {
|
|
24381
|
+
const biomeConfig = {
|
|
24382
|
+
$schema: "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
24383
|
+
vcs: {
|
|
24384
|
+
enabled: true,
|
|
24385
|
+
clientKind: "git",
|
|
24386
|
+
useIgnoreFile: true
|
|
24387
|
+
},
|
|
24388
|
+
files: {
|
|
24389
|
+
ignore: [
|
|
24390
|
+
"node_modules",
|
|
24391
|
+
"dist",
|
|
24392
|
+
"build",
|
|
24393
|
+
".next",
|
|
24394
|
+
".turbo",
|
|
24395
|
+
"coverage"
|
|
24396
|
+
]
|
|
24397
|
+
},
|
|
24398
|
+
formatter: {
|
|
24399
|
+
enabled: true,
|
|
24400
|
+
indentStyle: "space",
|
|
24401
|
+
indentWidth: 2,
|
|
24402
|
+
lineWidth: 100
|
|
24403
|
+
},
|
|
24404
|
+
linter: {
|
|
24405
|
+
enabled: true,
|
|
24406
|
+
rules: {
|
|
24407
|
+
recommended: true
|
|
24408
|
+
}
|
|
24409
|
+
},
|
|
24410
|
+
javascript: {
|
|
24411
|
+
formatter: {
|
|
24412
|
+
quoteStyle: "single",
|
|
24413
|
+
trailingCommas: "es5",
|
|
24414
|
+
semicolons: "always"
|
|
24415
|
+
}
|
|
24416
|
+
}
|
|
24417
|
+
};
|
|
24418
|
+
await writeFile(join(projectPath, "biome.json"), JSON.stringify(biomeConfig, null, 2));
|
|
24419
|
+
}
|
|
24420
|
+
function getCodeQualityDependencies(codeQuality) {
|
|
24421
|
+
if (codeQuality === "ultracite") {
|
|
24422
|
+
return ["ultracite", "@biomejs/biome"];
|
|
24423
|
+
}
|
|
24424
|
+
return ["@biomejs/biome"];
|
|
24425
|
+
}
|
|
24426
|
+
|
|
24427
|
+
// ../templates/src/generators/docker.ts
|
|
24428
|
+
async function setupDocker(projectPath, context) {
|
|
24429
|
+
const dockerfile = `# Use Bun official image
|
|
24430
|
+
FROM oven/bun:1 AS base
|
|
24431
|
+
|
|
24432
|
+
# Install dependencies
|
|
24433
|
+
FROM base AS deps
|
|
24434
|
+
WORKDIR /app
|
|
24435
|
+
COPY package.json bun.lock* ./
|
|
24436
|
+
RUN bun install --frozen-lockfile
|
|
24437
|
+
|
|
24438
|
+
# Build application
|
|
24439
|
+
FROM base AS builder
|
|
24440
|
+
WORKDIR /app
|
|
24441
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
24442
|
+
COPY . .
|
|
24443
|
+
|
|
24444
|
+
${context.preset === "web" || context.preset === "full" ? `# Build Next.js app
|
|
24445
|
+
RUN bun run build
|
|
24446
|
+
` : "# No build step needed for API/minimal"}
|
|
24447
|
+
# Production image
|
|
24448
|
+
FROM base AS runner
|
|
24449
|
+
WORKDIR /app
|
|
24450
|
+
|
|
24451
|
+
# Create non-root user
|
|
24452
|
+
RUN addgroup --system --gid 1001 bunuser && \\
|
|
24453
|
+
adduser --system --uid 1001 bunuser
|
|
24454
|
+
|
|
24455
|
+
${context.preset === "web" || context.preset === "full" ? `# Copy Next.js build
|
|
24456
|
+
COPY --from=builder --chown=bunuser:bunuser /app/.next/standalone ./
|
|
24457
|
+
COPY --from=builder --chown=bunuser:bunuser /app/.next/static ./.next/static
|
|
24458
|
+
COPY --from=builder --chown=bunuser:bunuser /app/public ./public
|
|
24459
|
+
` : `# Copy application
|
|
24460
|
+
COPY --from=builder --chown=bunuser:bunuser /app/src ./src
|
|
24461
|
+
COPY --from=builder --chown=bunuser:bunuser /app/package.json ./package.json
|
|
24462
|
+
`}
|
|
24463
|
+
USER bunuser
|
|
24464
|
+
|
|
24465
|
+
EXPOSE 3000
|
|
24466
|
+
|
|
24467
|
+
ENV PORT=3000
|
|
24468
|
+
ENV NODE_ENV=production
|
|
24469
|
+
|
|
24470
|
+
${context.preset === "web" || context.preset === "full" ? 'CMD ["bun", "run", "start"]' : 'CMD ["bun", "run", "src/index.ts"]'}
|
|
24471
|
+
`;
|
|
24472
|
+
await writeFile(join(projectPath, "Dockerfile"), dockerfile);
|
|
24473
|
+
const dockerCompose = `version: '3.8'
|
|
24474
|
+
|
|
24475
|
+
services:
|
|
24476
|
+
app:
|
|
24477
|
+
build:
|
|
24478
|
+
context: .
|
|
24479
|
+
dockerfile: Dockerfile
|
|
24480
|
+
ports:
|
|
24481
|
+
- "3000:3000"
|
|
24482
|
+
environment:
|
|
24483
|
+
- NODE_ENV=production
|
|
24484
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? "- DATABASE_URL=${DATABASE_URL:-postgres://postgres:postgres@db:5432/${context.projectName}}" : ""}
|
|
24485
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `depends_on:
|
|
24486
|
+
- db` : ""}
|
|
24487
|
+
restart: unless-stopped
|
|
24488
|
+
|
|
24489
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `db:
|
|
24490
|
+
image: ${context.database === "sqlite-drizzle" ? "alpine:latest" : "postgres:16-alpine"}
|
|
24491
|
+
${context.database !== "sqlite-drizzle" ? `environment:
|
|
24492
|
+
- POSTGRES_USER=postgres
|
|
24493
|
+
- POSTGRES_PASSWORD=postgres
|
|
24494
|
+
- POSTGRES_DB=${context.projectName}
|
|
24495
|
+
volumes:
|
|
24496
|
+
- postgres_data:/var/lib/postgresql/data
|
|
24497
|
+
ports:
|
|
24498
|
+
- "5432:5432"` : `volumes:
|
|
24499
|
+
- sqlite_data:/data`}
|
|
24500
|
+
restart: unless-stopped
|
|
24501
|
+
` : ""}
|
|
24502
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `volumes:
|
|
24503
|
+
${context.database === "sqlite-drizzle" ? "sqlite_data:" : "postgres_data:"}` : ""}
|
|
24504
|
+
`;
|
|
24505
|
+
await writeFile(join(projectPath, "docker-compose.yml"), dockerCompose);
|
|
24506
|
+
const dockerignore = `# Dependencies
|
|
24507
|
+
node_modules/
|
|
24508
|
+
bun.lock
|
|
24509
|
+
|
|
24510
|
+
# Build
|
|
24511
|
+
dist/
|
|
24512
|
+
build/
|
|
24513
|
+
.next/
|
|
24514
|
+
.turbo/
|
|
24515
|
+
|
|
24516
|
+
# Environment
|
|
24517
|
+
.env
|
|
24518
|
+
.env*.local
|
|
24519
|
+
|
|
24520
|
+
# Logs
|
|
24521
|
+
*.log
|
|
24522
|
+
npm-debug.log*
|
|
24523
|
+
|
|
24524
|
+
# OS
|
|
24525
|
+
.DS_Store
|
|
24526
|
+
|
|
24527
|
+
# IDEs
|
|
24528
|
+
.vscode/
|
|
24529
|
+
.idea/
|
|
24530
|
+
|
|
24531
|
+
# Git
|
|
24532
|
+
.git/
|
|
24533
|
+
.gitignore
|
|
24534
|
+
|
|
24535
|
+
# Documentation
|
|
24536
|
+
README.md
|
|
24537
|
+
CLAUDE.md
|
|
24538
|
+
.cursorrules
|
|
24539
|
+
.windsurfrules
|
|
24540
|
+
|
|
24541
|
+
# Tests
|
|
24542
|
+
*.test.ts
|
|
24543
|
+
*.test.tsx
|
|
24544
|
+
*.spec.ts
|
|
24545
|
+
*.spec.tsx
|
|
24546
|
+
coverage/
|
|
24547
|
+
`;
|
|
24548
|
+
await writeFile(join(projectPath, ".dockerignore"), dockerignore);
|
|
24549
|
+
}
|
|
24550
|
+
|
|
24551
|
+
// ../templates/src/generators/cicd.ts
|
|
24552
|
+
async function setupGitHubActions(projectPath, context) {
|
|
24553
|
+
await ensureDirectory(join(projectPath, ".github/workflows"));
|
|
24554
|
+
const ciWorkflow = `name: CI
|
|
24555
|
+
|
|
24556
|
+
on:
|
|
24557
|
+
push:
|
|
24558
|
+
branches: [main, develop]
|
|
24559
|
+
pull_request:
|
|
24560
|
+
branches: [main, develop]
|
|
24561
|
+
|
|
24562
|
+
jobs:
|
|
24563
|
+
lint:
|
|
24564
|
+
name: Lint & Format Check
|
|
24565
|
+
runs-on: ubuntu-latest
|
|
24566
|
+
|
|
24567
|
+
steps:
|
|
24568
|
+
- name: Checkout code
|
|
24569
|
+
uses: actions/checkout@v4
|
|
24570
|
+
|
|
24571
|
+
- name: Setup Bun
|
|
24572
|
+
uses: oven-sh/setup-bun@v2
|
|
24573
|
+
with:
|
|
24574
|
+
bun-version: latest
|
|
24575
|
+
|
|
24576
|
+
- name: Install dependencies
|
|
24577
|
+
run: bun install --frozen-lockfile
|
|
24578
|
+
|
|
24579
|
+
- name: Run linter
|
|
24580
|
+
run: bun run lint
|
|
24581
|
+
|
|
24582
|
+
typecheck:
|
|
24583
|
+
name: Type Check
|
|
24584
|
+
runs-on: ubuntu-latest
|
|
24585
|
+
|
|
24586
|
+
steps:
|
|
24587
|
+
- name: Checkout code
|
|
24588
|
+
uses: actions/checkout@v4
|
|
24589
|
+
|
|
24590
|
+
- name: Setup Bun
|
|
24591
|
+
uses: oven-sh/setup-bun@v2
|
|
24592
|
+
with:
|
|
24593
|
+
bun-version: latest
|
|
24594
|
+
|
|
24595
|
+
- name: Install dependencies
|
|
24596
|
+
run: bun install --frozen-lockfile
|
|
24597
|
+
|
|
24598
|
+
- name: Type check
|
|
24599
|
+
run: ${context.preset === "full" ? "bun run --filter '*' typecheck" : "bun run typecheck"}
|
|
24600
|
+
|
|
24601
|
+
${context.testing !== "none" ? `test:
|
|
24602
|
+
name: Run Tests
|
|
24603
|
+
runs-on: ubuntu-latest
|
|
24604
|
+
|
|
24605
|
+
steps:
|
|
24606
|
+
- name: Checkout code
|
|
24607
|
+
uses: actions/checkout@v4
|
|
24608
|
+
|
|
24609
|
+
- name: Setup Bun
|
|
24610
|
+
uses: oven-sh/setup-bun@v2
|
|
24611
|
+
with:
|
|
24612
|
+
bun-version: latest
|
|
24613
|
+
|
|
24614
|
+
- name: Install dependencies
|
|
24615
|
+
run: bun install --frozen-lockfile
|
|
24616
|
+
|
|
24617
|
+
- name: Run tests
|
|
24618
|
+
run: ${context.testing === "bun-test" ? "bun test" : "bun run test"}
|
|
24619
|
+
${context.database && context.database !== "none" ? `env:
|
|
24620
|
+
DATABASE_URL: sqlite://test.db` : ""}
|
|
24621
|
+
` : ""}
|
|
24622
|
+
build:
|
|
24623
|
+
name: Build
|
|
24624
|
+
runs-on: ubuntu-latest
|
|
24625
|
+
needs: [lint, typecheck${context.testing !== "none" ? ", test" : ""}]
|
|
24626
|
+
|
|
24627
|
+
steps:
|
|
24628
|
+
- name: Checkout code
|
|
24629
|
+
uses: actions/checkout@v4
|
|
24630
|
+
|
|
24631
|
+
- name: Setup Bun
|
|
24632
|
+
uses: oven-sh/setup-bun@v2
|
|
24633
|
+
with:
|
|
24634
|
+
bun-version: latest
|
|
24635
|
+
|
|
24636
|
+
- name: Install dependencies
|
|
24637
|
+
run: bun install --frozen-lockfile
|
|
24638
|
+
|
|
24639
|
+
${context.preset === "web" || context.preset === "full" ? `- name: Build application
|
|
24640
|
+
run: bun run build
|
|
24641
|
+
env:
|
|
24642
|
+
${context.database === "supabase" ? `NEXT_PUBLIC_SUPABASE_URL: \${{ secrets.SUPABASE_URL }}
|
|
24643
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY: \${{ secrets.SUPABASE_ANON_KEY }}
|
|
24644
|
+
` : ""}NODE_ENV: production` : ""}
|
|
24645
|
+
|
|
24646
|
+
${context.docker ? `- name: Set up Docker Buildx
|
|
24647
|
+
uses: docker/setup-buildx-action@v3
|
|
24648
|
+
|
|
24649
|
+
- name: Build Docker image
|
|
24650
|
+
uses: docker/build-push-action@v5
|
|
24651
|
+
with:
|
|
24652
|
+
context: .
|
|
24653
|
+
push: false
|
|
24654
|
+
tags: ${context.projectName}:latest
|
|
24655
|
+
cache-from: type=gha
|
|
24656
|
+
cache-to: type=gha,mode=max` : ""}
|
|
24657
|
+
`;
|
|
24658
|
+
await writeFile(join(projectPath, ".github/workflows/ci.yml"), ciWorkflow);
|
|
24659
|
+
const deployWorkflow = `# name: Deploy
|
|
24660
|
+
#
|
|
24661
|
+
# on:
|
|
24662
|
+
# push:
|
|
24663
|
+
# branches: [main]
|
|
24664
|
+
#
|
|
24665
|
+
# jobs:
|
|
24666
|
+
# deploy:
|
|
24667
|
+
# name: Deploy to Production
|
|
24668
|
+
# runs-on: ubuntu-latest
|
|
24669
|
+
# environment: production
|
|
24670
|
+
#
|
|
24671
|
+
# steps:
|
|
24672
|
+
# - name: Checkout code
|
|
24673
|
+
# uses: actions/checkout@v4
|
|
24674
|
+
#
|
|
24675
|
+
# - name: Setup Bun
|
|
24676
|
+
# uses: oven-sh/setup-bun@v2
|
|
24677
|
+
# with:
|
|
24678
|
+
# bun-version: latest
|
|
24679
|
+
#
|
|
24680
|
+
# - name: Install dependencies
|
|
24681
|
+
# run: bun install --frozen-lockfile
|
|
24682
|
+
#
|
|
24683
|
+
# - name: Build
|
|
24684
|
+
# run: bun run build
|
|
24685
|
+
# env:
|
|
24686
|
+
# NODE_ENV: production
|
|
24687
|
+
# ${context.database === "supabase" ? `NEXT_PUBLIC_SUPABASE_URL: \${{ secrets.SUPABASE_URL }}
|
|
24688
|
+
# NEXT_PUBLIC_SUPABASE_ANON_KEY: \${{ secrets.SUPABASE_ANON_KEY }}` : ""}
|
|
24689
|
+
#
|
|
24690
|
+
# # Add your deployment steps here
|
|
24691
|
+
# # Examples:
|
|
24692
|
+
# # - Deploy to Vercel
|
|
24693
|
+
# # - Deploy to Railway
|
|
24694
|
+
# # - Deploy to your own server via SSH
|
|
24695
|
+
# # - Push Docker image to registry
|
|
24696
|
+
`;
|
|
24697
|
+
await writeFile(join(projectPath, ".github/workflows/deploy.yml.example"), deployWorkflow);
|
|
24698
|
+
const dependabotConfig = `version: 2
|
|
24699
|
+
updates:
|
|
24700
|
+
- package-ecosystem: "npm"
|
|
24701
|
+
directory: "/"
|
|
24702
|
+
schedule:
|
|
24703
|
+
interval: "weekly"
|
|
24704
|
+
open-pull-requests-limit: 10
|
|
24705
|
+
reviewers:
|
|
24706
|
+
- "your-github-username"
|
|
24707
|
+
labels:
|
|
24708
|
+
- "dependencies"
|
|
24709
|
+
|
|
24710
|
+
${context.docker ? `- package-ecosystem: "docker"
|
|
24711
|
+
directory: "/"
|
|
24712
|
+
schedule:
|
|
24713
|
+
interval: "weekly"
|
|
24714
|
+
labels:
|
|
24715
|
+
- "dependencies"
|
|
24716
|
+
- "docker"` : ""}
|
|
24717
|
+
|
|
24718
|
+
- package-ecosystem: "github-actions"
|
|
24719
|
+
directory: "/"
|
|
24720
|
+
schedule:
|
|
24721
|
+
interval: "weekly"
|
|
24722
|
+
labels:
|
|
24723
|
+
- "dependencies"
|
|
24724
|
+
- "ci"
|
|
24725
|
+
`;
|
|
24726
|
+
await writeFile(join(projectPath, ".github/dependabot.yml"), dependabotConfig);
|
|
24727
|
+
}
|
|
24728
|
+
|
|
24164
24729
|
// ../templates/src/builders/web.ts
|
|
24165
24730
|
async function buildWebPreset(projectPath, context) {
|
|
24166
24731
|
await ensureDirectory(join(projectPath, "src/app"));
|
|
@@ -24234,51 +24799,226 @@ const config: Config = {
|
|
|
24234
24799
|
export default config;
|
|
24235
24800
|
`;
|
|
24236
24801
|
await writeFile(join(projectPath, "tailwind.config.ts"), tailwindConfigContent);
|
|
24237
|
-
const
|
|
24238
|
-
|
|
24802
|
+
const getTsCompilerOptions = () => {
|
|
24803
|
+
const baseOptions = {
|
|
24239
24804
|
target: "ES2017",
|
|
24240
24805
|
lib: ["dom", "dom.iterable", "esnext"],
|
|
24241
24806
|
allowJs: true,
|
|
24242
24807
|
skipLibCheck: true,
|
|
24243
|
-
strict: true,
|
|
24244
24808
|
noEmit: true,
|
|
24245
24809
|
esModuleInterop: true,
|
|
24246
24810
|
module: "esnext",
|
|
24247
24811
|
moduleResolution: "bundler",
|
|
24248
24812
|
resolveJsonModule: true,
|
|
24249
24813
|
isolatedModules: true,
|
|
24250
|
-
jsx: "
|
|
24814
|
+
jsx: "react-jsx",
|
|
24251
24815
|
incremental: true,
|
|
24252
24816
|
plugins: [{ name: "next" }],
|
|
24253
|
-
paths: {
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24817
|
+
paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
|
|
24818
|
+
};
|
|
24819
|
+
if (context.tsStrictness === "strict") {
|
|
24820
|
+
return {
|
|
24821
|
+
...baseOptions,
|
|
24822
|
+
strict: true,
|
|
24823
|
+
noUnusedLocals: true,
|
|
24824
|
+
noUnusedParameters: true,
|
|
24825
|
+
noFallthroughCasesInSwitch: true,
|
|
24826
|
+
noImplicitReturns: true
|
|
24827
|
+
};
|
|
24828
|
+
}
|
|
24829
|
+
if (context.tsStrictness === "moderate") {
|
|
24830
|
+
return {
|
|
24831
|
+
...baseOptions,
|
|
24832
|
+
strict: true,
|
|
24833
|
+
noUnusedLocals: false,
|
|
24834
|
+
noUnusedParameters: false
|
|
24835
|
+
};
|
|
24836
|
+
}
|
|
24837
|
+
return {
|
|
24838
|
+
...baseOptions,
|
|
24839
|
+
strict: false,
|
|
24840
|
+
noImplicitAny: false
|
|
24841
|
+
};
|
|
24842
|
+
};
|
|
24843
|
+
const tsconfigContent = {
|
|
24844
|
+
compilerOptions: getTsCompilerOptions(),
|
|
24845
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
|
|
24258
24846
|
exclude: ["node_modules"]
|
|
24259
24847
|
};
|
|
24260
24848
|
await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24274
|
-
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24849
|
+
if (context.codeQuality === "ultracite") {
|
|
24850
|
+
await setupUltracite(projectPath, context);
|
|
24851
|
+
} else {
|
|
24852
|
+
await setupBiome(projectPath, context);
|
|
24853
|
+
}
|
|
24854
|
+
if (context.docker) {
|
|
24855
|
+
await setupDocker(projectPath, context);
|
|
24856
|
+
}
|
|
24857
|
+
if (context.cicd) {
|
|
24858
|
+
await setupGitHubActions(projectPath, context);
|
|
24859
|
+
}
|
|
24860
|
+
}
|
|
24861
|
+
// ../templates/src/generators/database.ts
|
|
24862
|
+
async function setupPostgresDrizzle(projectPath, context, isMonorepo = false) {
|
|
24863
|
+
const dbPath = isMonorepo ? join(projectPath, "packages/db") : join(projectPath, "src/db");
|
|
24864
|
+
await ensureDirectory(join(dbPath, "schema"));
|
|
24865
|
+
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
24866
|
+
|
|
24867
|
+
export default defineConfig({
|
|
24868
|
+
schema: './src/schema/index.ts',
|
|
24869
|
+
out: './drizzle',
|
|
24870
|
+
dialect: 'postgresql',
|
|
24871
|
+
dbCredentials: {
|
|
24872
|
+
url: process.env.DATABASE_URL!,
|
|
24873
|
+
},
|
|
24874
|
+
});
|
|
24875
|
+
`;
|
|
24876
|
+
await writeFile(join(isMonorepo ? join(projectPath, "packages/db") : projectPath, "drizzle.config.ts"), drizzleConfig);
|
|
24877
|
+
const clientContent = `import { drizzle } from 'drizzle-orm/bun-postgres';
|
|
24878
|
+
import { Database } from 'bun:postgres';
|
|
24879
|
+
import * as schema from './schema';
|
|
24880
|
+
|
|
24881
|
+
const client = new Database(process.env.DATABASE_URL!);
|
|
24882
|
+
export const db = drizzle(client, { schema });
|
|
24883
|
+
`;
|
|
24884
|
+
await writeFile(join(dbPath, "index.ts"), clientContent);
|
|
24885
|
+
const schemaContent = `import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
|
24886
|
+
|
|
24887
|
+
export const users = pgTable('users', {
|
|
24888
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
24889
|
+
email: text('email').notNull().unique(),
|
|
24890
|
+
name: text('name'),
|
|
24891
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
24892
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
24893
|
+
});
|
|
24894
|
+
`;
|
|
24895
|
+
await writeFile(join(dbPath, "schema/index.ts"), schemaContent);
|
|
24896
|
+
const envExample = `# Database
|
|
24897
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${context.projectName}
|
|
24898
|
+
`;
|
|
24899
|
+
await writeFile(join(projectPath, ".env.example"), envExample);
|
|
24900
|
+
}
|
|
24901
|
+
async function setupSupabase(projectPath, context, isMonorepo = false) {
|
|
24902
|
+
const dbPath = isMonorepo ? join(projectPath, "packages/db") : join(projectPath, "src/db");
|
|
24903
|
+
await ensureDirectory(join(dbPath, "schema"));
|
|
24904
|
+
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
24905
|
+
|
|
24906
|
+
export default defineConfig({
|
|
24907
|
+
schema: './src/schema/index.ts',
|
|
24908
|
+
out: './supabase/migrations',
|
|
24909
|
+
dialect: 'postgresql',
|
|
24910
|
+
dbCredentials: {
|
|
24911
|
+
url: process.env.DATABASE_URL!,
|
|
24912
|
+
},
|
|
24913
|
+
});
|
|
24914
|
+
`;
|
|
24915
|
+
await writeFile(join(isMonorepo ? join(projectPath, "packages/db") : projectPath, "drizzle.config.ts"), drizzleConfig);
|
|
24916
|
+
const clientContent = `import { createClient } from '@supabase/supabase-js';
|
|
24917
|
+
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
24918
|
+
import postgres from 'postgres';
|
|
24919
|
+
import * as schema from './schema';
|
|
24920
|
+
|
|
24921
|
+
// Supabase client for auth, storage, realtime
|
|
24922
|
+
export const supabase = createClient(
|
|
24923
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
24924
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
24925
|
+
);
|
|
24926
|
+
|
|
24927
|
+
// Drizzle client for type-safe database queries
|
|
24928
|
+
const queryClient = postgres(process.env.DATABASE_URL!);
|
|
24929
|
+
export const db = drizzle(queryClient, { schema });
|
|
24930
|
+
`;
|
|
24931
|
+
await writeFile(join(dbPath, "index.ts"), clientContent);
|
|
24932
|
+
const schemaContent = `import { pgTable, text, timestamp, uuid, boolean } from 'drizzle-orm/pg-core';
|
|
24933
|
+
|
|
24934
|
+
export const users = pgTable('users', {
|
|
24935
|
+
id: uuid('id').primaryKey(),
|
|
24936
|
+
email: text('email').notNull().unique(),
|
|
24937
|
+
name: text('name'),
|
|
24938
|
+
avatarUrl: text('avatar_url'),
|
|
24939
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
24940
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
24941
|
+
});
|
|
24942
|
+
|
|
24943
|
+
export const profiles = pgTable('profiles', {
|
|
24944
|
+
id: uuid('id').primaryKey().references(() => users.id),
|
|
24945
|
+
username: text('username').unique(),
|
|
24946
|
+
bio: text('bio'),
|
|
24947
|
+
website: text('website'),
|
|
24948
|
+
isPublic: boolean('is_public').default(true),
|
|
24949
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
24950
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
24951
|
+
});
|
|
24952
|
+
`;
|
|
24953
|
+
await writeFile(join(dbPath, "schema/index.ts"), schemaContent);
|
|
24954
|
+
await ensureDirectory(join(projectPath, "supabase/migrations"));
|
|
24955
|
+
const envExample = `# Supabase
|
|
24956
|
+
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
24957
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
24958
|
+
DATABASE_URL=postgresql://postgres:[password]@db.your-project.supabase.co:5432/postgres
|
|
24959
|
+
`;
|
|
24960
|
+
await writeFile(join(projectPath, ".env.example"), envExample);
|
|
24961
|
+
}
|
|
24962
|
+
async function setupSQLiteDrizzle(projectPath, context, isMonorepo = false) {
|
|
24963
|
+
const dbPath = isMonorepo ? join(projectPath, "packages/db") : join(projectPath, "src/db");
|
|
24964
|
+
await ensureDirectory(join(dbPath, "schema"));
|
|
24965
|
+
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
24966
|
+
|
|
24967
|
+
export default defineConfig({
|
|
24968
|
+
schema: './src/schema/index.ts',
|
|
24969
|
+
out: './drizzle',
|
|
24970
|
+
dialect: 'sqlite',
|
|
24971
|
+
dbCredentials: {
|
|
24972
|
+
url: process.env.DATABASE_URL || './local.db',
|
|
24973
|
+
},
|
|
24974
|
+
});
|
|
24975
|
+
`;
|
|
24976
|
+
await writeFile(join(isMonorepo ? join(projectPath, "packages/db") : projectPath, "drizzle.config.ts"), drizzleConfig);
|
|
24977
|
+
const clientContent = `import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
24978
|
+
import { Database } from 'bun:sqlite';
|
|
24979
|
+
import * as schema from './schema';
|
|
24980
|
+
|
|
24981
|
+
const sqlite = new Database(process.env.DATABASE_URL || './local.db');
|
|
24982
|
+
export const db = drizzle(sqlite, { schema });
|
|
24983
|
+
`;
|
|
24984
|
+
await writeFile(join(dbPath, "index.ts"), clientContent);
|
|
24985
|
+
const schemaContent = `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
24986
|
+
import { sql } from 'drizzle-orm';
|
|
24987
|
+
|
|
24988
|
+
export const users = sqliteTable('users', {
|
|
24989
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
24990
|
+
email: text('email').notNull().unique(),
|
|
24991
|
+
name: text('name'),
|
|
24992
|
+
createdAt: integer('created_at', { mode: 'timestamp' }).default(sql\`(unixepoch())\`).notNull(),
|
|
24993
|
+
updatedAt: integer('updated_at', { mode: 'timestamp' }).default(sql\`(unixepoch())\`).notNull(),
|
|
24994
|
+
});
|
|
24995
|
+
`;
|
|
24996
|
+
await writeFile(join(dbPath, "schema/index.ts"), schemaContent);
|
|
24997
|
+
const envExample = `# Database
|
|
24998
|
+
DATABASE_URL=./local.db
|
|
24999
|
+
`;
|
|
25000
|
+
await writeFile(join(projectPath, ".env.example"), envExample);
|
|
25001
|
+
const gitignoreAddition = `
|
|
25002
|
+
# SQLite
|
|
25003
|
+
*.db
|
|
25004
|
+
*.db-shm
|
|
25005
|
+
*.db-wal
|
|
25006
|
+
`;
|
|
25007
|
+
await writeFile(join(projectPath, ".gitignore.db"), gitignoreAddition);
|
|
24281
25008
|
}
|
|
25009
|
+
function getDatabaseDependencies(databaseType) {
|
|
25010
|
+
switch (databaseType) {
|
|
25011
|
+
case "postgres-drizzle":
|
|
25012
|
+
return ["drizzle-orm", "drizzle-kit"];
|
|
25013
|
+
case "supabase":
|
|
25014
|
+
return ["@supabase/supabase-js", "drizzle-orm", "drizzle-kit", "postgres"];
|
|
25015
|
+
case "sqlite-drizzle":
|
|
25016
|
+
return ["drizzle-orm", "drizzle-kit"];
|
|
25017
|
+
default:
|
|
25018
|
+
return [];
|
|
25019
|
+
}
|
|
25020
|
+
}
|
|
25021
|
+
|
|
24282
25022
|
// ../templates/src/builders/api.ts
|
|
24283
25023
|
async function buildApiPreset(projectPath, context) {
|
|
24284
25024
|
await ensureDirectory(join(projectPath, "src/routes"));
|
|
@@ -24287,6 +25027,9 @@ async function buildApiPreset(projectPath, context) {
|
|
|
24287
25027
|
import { serve } from 'bun';
|
|
24288
25028
|
import { logger } from 'hono/logger';
|
|
24289
25029
|
import { cors } from 'hono/cors';
|
|
25030
|
+
${context.database && context.database !== "none" ? `import { db } from './db';
|
|
25031
|
+
import { users } from './db/schema';
|
|
25032
|
+
import { eq } from 'drizzle-orm';` : ""}
|
|
24290
25033
|
|
|
24291
25034
|
const app = new Hono();
|
|
24292
25035
|
|
|
@@ -24299,6 +25042,7 @@ app.get('/', (context) => {
|
|
|
24299
25042
|
return context.json({
|
|
24300
25043
|
message: 'Welcome to ${context.projectName} API \uD83C\uDF5E',
|
|
24301
25044
|
version: '1.0.0',
|
|
25045
|
+
database: '${context.database || "none"}',
|
|
24302
25046
|
});
|
|
24303
25047
|
});
|
|
24304
25048
|
|
|
@@ -24309,6 +25053,44 @@ app.get('/health', (context) => {
|
|
|
24309
25053
|
});
|
|
24310
25054
|
});
|
|
24311
25055
|
|
|
25056
|
+
${context.database && context.database !== "none" ? `// Database example routes
|
|
25057
|
+
app.get('/users', async (context) => {
|
|
25058
|
+
try {
|
|
25059
|
+
const allUsers = await db.select().from(users);
|
|
25060
|
+
return context.json({ users: allUsers });
|
|
25061
|
+
} catch (error) {
|
|
25062
|
+
console.error('Database error:', error);
|
|
25063
|
+
return context.json({ error: 'Failed to fetch users' }, 500);
|
|
25064
|
+
}
|
|
25065
|
+
});
|
|
25066
|
+
|
|
25067
|
+
app.get('/users/:id', async (context) => {
|
|
25068
|
+
try {
|
|
25069
|
+
const id = context.req.param('id');
|
|
25070
|
+
const user = await db.select().from(users).where(eq(users.id, id)).limit(1);
|
|
25071
|
+
|
|
25072
|
+
if (!user.length) {
|
|
25073
|
+
return context.json({ error: 'User not found' }, 404);
|
|
25074
|
+
}
|
|
25075
|
+
|
|
25076
|
+
return context.json({ user: user[0] });
|
|
25077
|
+
} catch (error) {
|
|
25078
|
+
console.error('Database error:', error);
|
|
25079
|
+
return context.json({ error: 'Failed to fetch user' }, 500);
|
|
25080
|
+
}
|
|
25081
|
+
});
|
|
25082
|
+
|
|
25083
|
+
app.post('/users', async (context) => {
|
|
25084
|
+
try {
|
|
25085
|
+
const body = await context.req.json();
|
|
25086
|
+
const newUser = await db.insert(users).values(body).returning();
|
|
25087
|
+
return context.json({ user: newUser[0] }, 201);
|
|
25088
|
+
} catch (error) {
|
|
25089
|
+
console.error('Database error:', error);
|
|
25090
|
+
return context.json({ error: 'Failed to create user' }, 500);
|
|
25091
|
+
}
|
|
25092
|
+
});
|
|
25093
|
+
` : ""}
|
|
24312
25094
|
// 404 handler
|
|
24313
25095
|
app.notFound((context) => {
|
|
24314
25096
|
return context.json({ error: 'Not found' }, 404);
|
|
@@ -24330,7 +25112,8 @@ serve({
|
|
|
24330
25112
|
},
|
|
24331
25113
|
});
|
|
24332
25114
|
|
|
24333
|
-
console.log('\uD83D\uDE80 API running on http://localhost:3001');
|
|
25115
|
+
console.log('\uD83D\uDE80 ${context.projectName} API running on http://localhost:3001');
|
|
25116
|
+
${context.database && context.database !== "none" ? "console.log('\uD83D\uDCCA Database:', process.env.DATABASE_URL || 'Not configured');" : ""}
|
|
24334
25117
|
`;
|
|
24335
25118
|
await writeFile(join(projectPath, "src/index.ts"), indexContent);
|
|
24336
25119
|
const usersRouteContent = `import { Hono } from 'hono';
|
|
@@ -24354,8 +25137,8 @@ users.get('/:id', (context) => {
|
|
|
24354
25137
|
export default users;
|
|
24355
25138
|
`;
|
|
24356
25139
|
await writeFile(join(projectPath, "src/routes/users.ts"), usersRouteContent);
|
|
24357
|
-
const
|
|
24358
|
-
|
|
25140
|
+
const getTsCompilerOptions = () => {
|
|
25141
|
+
const baseOptions = {
|
|
24359
25142
|
lib: ["ESNext"],
|
|
24360
25143
|
target: "ESNext",
|
|
24361
25144
|
module: "ESNext",
|
|
@@ -24365,20 +25148,63 @@ export default users;
|
|
|
24365
25148
|
allowImportingTsExtensions: true,
|
|
24366
25149
|
verbatimModuleSyntax: true,
|
|
24367
25150
|
noEmit: true,
|
|
24368
|
-
strict: true,
|
|
24369
25151
|
skipLibCheck: true,
|
|
24370
|
-
|
|
24371
|
-
|
|
25152
|
+
types: ["bun"],
|
|
25153
|
+
paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
|
|
25154
|
+
};
|
|
25155
|
+
if (context.tsStrictness === "strict") {
|
|
25156
|
+
return {
|
|
25157
|
+
...baseOptions,
|
|
25158
|
+
strict: true,
|
|
25159
|
+
noUnusedLocals: true,
|
|
25160
|
+
noUnusedParameters: true,
|
|
25161
|
+
noFallthroughCasesInSwitch: true,
|
|
25162
|
+
noImplicitReturns: true
|
|
25163
|
+
};
|
|
25164
|
+
}
|
|
25165
|
+
if (context.tsStrictness === "moderate") {
|
|
25166
|
+
return {
|
|
25167
|
+
...baseOptions,
|
|
25168
|
+
strict: true,
|
|
25169
|
+
noFallthroughCasesInSwitch: true
|
|
25170
|
+
};
|
|
24372
25171
|
}
|
|
25172
|
+
return {
|
|
25173
|
+
...baseOptions,
|
|
25174
|
+
strict: false
|
|
25175
|
+
};
|
|
25176
|
+
};
|
|
25177
|
+
const tsconfigContent = {
|
|
25178
|
+
compilerOptions: getTsCompilerOptions()
|
|
24373
25179
|
};
|
|
24374
25180
|
await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
|
|
24375
25181
|
const bunfigContent = `[install]
|
|
24376
25182
|
frozenLockfile = false
|
|
24377
25183
|
|
|
24378
|
-
[test]
|
|
25184
|
+
${context.testing !== "none" ? `[test]
|
|
24379
25185
|
coverage = true
|
|
24380
|
-
`;
|
|
25186
|
+
` : ""}`;
|
|
24381
25187
|
await writeFile(join(projectPath, "bunfig.toml"), bunfigContent);
|
|
25188
|
+
if (context.database && context.database !== "none") {
|
|
25189
|
+
if (context.database === "postgres-drizzle") {
|
|
25190
|
+
await setupPostgresDrizzle(projectPath, context, false);
|
|
25191
|
+
} else if (context.database === "supabase") {
|
|
25192
|
+
await setupSupabase(projectPath, context, false);
|
|
25193
|
+
} else if (context.database === "sqlite-drizzle") {
|
|
25194
|
+
await setupSQLiteDrizzle(projectPath, context, false);
|
|
25195
|
+
}
|
|
25196
|
+
}
|
|
25197
|
+
if (context.codeQuality === "ultracite") {
|
|
25198
|
+
await setupUltracite(projectPath, context);
|
|
25199
|
+
} else {
|
|
25200
|
+
await setupBiome(projectPath, context);
|
|
25201
|
+
}
|
|
25202
|
+
if (context.docker) {
|
|
25203
|
+
await setupDocker(projectPath, context);
|
|
25204
|
+
}
|
|
25205
|
+
if (context.cicd) {
|
|
25206
|
+
await setupGitHubActions(projectPath, context);
|
|
25207
|
+
}
|
|
24382
25208
|
}
|
|
24383
25209
|
// ../templates/src/builders/full.ts
|
|
24384
25210
|
async function buildFullPreset(projectPath, context) {
|
|
@@ -24387,6 +25213,9 @@ async function buildFullPreset(projectPath, context) {
|
|
|
24387
25213
|
await ensureDirectory(join(projectPath, "apps/api"));
|
|
24388
25214
|
await ensureDirectory(join(projectPath, "packages/types"));
|
|
24389
25215
|
await ensureDirectory(join(projectPath, "packages/utils"));
|
|
25216
|
+
if (context.database && context.database !== "none") {
|
|
25217
|
+
await ensureDirectory(join(projectPath, "packages/db"));
|
|
25218
|
+
}
|
|
24390
25219
|
const rootPackageJson = {
|
|
24391
25220
|
name: `${context.packageName}-monorepo`,
|
|
24392
25221
|
version: "0.0.0",
|
|
@@ -24620,31 +25449,50 @@ const config: Config = {
|
|
|
24620
25449
|
export default config;
|
|
24621
25450
|
`;
|
|
24622
25451
|
await writeFile(join(projectPath, "apps/web/tailwind.config.ts"), webTailwindConfigContent);
|
|
24623
|
-
const
|
|
24624
|
-
|
|
25452
|
+
const getWebTsConfig = () => {
|
|
25453
|
+
const baseOptions = {
|
|
24625
25454
|
target: "ES2017",
|
|
24626
25455
|
lib: ["dom", "dom.iterable", "esnext"],
|
|
24627
25456
|
allowJs: true,
|
|
24628
25457
|
skipLibCheck: true,
|
|
24629
|
-
strict: true,
|
|
24630
25458
|
noEmit: true,
|
|
24631
25459
|
esModuleInterop: true,
|
|
24632
25460
|
module: "esnext",
|
|
24633
25461
|
moduleResolution: "bundler",
|
|
24634
25462
|
resolveJsonModule: true,
|
|
24635
25463
|
isolatedModules: true,
|
|
24636
|
-
jsx: "
|
|
25464
|
+
jsx: "react-jsx",
|
|
24637
25465
|
incremental: true,
|
|
24638
|
-
plugins: [
|
|
24639
|
-
|
|
24640
|
-
|
|
24641
|
-
|
|
24642
|
-
|
|
24643
|
-
|
|
24644
|
-
|
|
24645
|
-
|
|
24646
|
-
|
|
24647
|
-
|
|
25466
|
+
plugins: [{ name: "next" }],
|
|
25467
|
+
paths: context.pathAliases ? { "@/*": ["./src/*"] } : undefined
|
|
25468
|
+
};
|
|
25469
|
+
if (context.tsStrictness === "strict") {
|
|
25470
|
+
return {
|
|
25471
|
+
...baseOptions,
|
|
25472
|
+
strict: true,
|
|
25473
|
+
noUnusedLocals: true,
|
|
25474
|
+
noUnusedParameters: true,
|
|
25475
|
+
noFallthroughCasesInSwitch: true,
|
|
25476
|
+
noImplicitReturns: true
|
|
25477
|
+
};
|
|
25478
|
+
}
|
|
25479
|
+
if (context.tsStrictness === "moderate") {
|
|
25480
|
+
return {
|
|
25481
|
+
...baseOptions,
|
|
25482
|
+
strict: true,
|
|
25483
|
+
noUnusedLocals: false,
|
|
25484
|
+
noUnusedParameters: false
|
|
25485
|
+
};
|
|
25486
|
+
}
|
|
25487
|
+
return {
|
|
25488
|
+
...baseOptions,
|
|
25489
|
+
strict: false,
|
|
25490
|
+
noImplicitAny: false
|
|
25491
|
+
};
|
|
25492
|
+
};
|
|
25493
|
+
const webTsconfigContent = {
|
|
25494
|
+
compilerOptions: getWebTsConfig(),
|
|
25495
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
|
|
24648
25496
|
exclude: ["node_modules"]
|
|
24649
25497
|
};
|
|
24650
25498
|
await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify(webTsconfigContent, null, 2));
|
|
@@ -24734,30 +25582,8 @@ export default config;
|
|
|
24734
25582
|
`;
|
|
24735
25583
|
await writeFile(join(projectPath, "apps/platform/tailwind.config.ts"), platformTailwindConfigContent);
|
|
24736
25584
|
const platformTsconfigContent = {
|
|
24737
|
-
compilerOptions:
|
|
24738
|
-
|
|
24739
|
-
lib: ["dom", "dom.iterable", "esnext"],
|
|
24740
|
-
allowJs: true,
|
|
24741
|
-
skipLibCheck: true,
|
|
24742
|
-
strict: true,
|
|
24743
|
-
noEmit: true,
|
|
24744
|
-
esModuleInterop: true,
|
|
24745
|
-
module: "esnext",
|
|
24746
|
-
moduleResolution: "bundler",
|
|
24747
|
-
resolveJsonModule: true,
|
|
24748
|
-
isolatedModules: true,
|
|
24749
|
-
jsx: "preserve",
|
|
24750
|
-
incremental: true,
|
|
24751
|
-
plugins: [
|
|
24752
|
-
{
|
|
24753
|
-
name: "next"
|
|
24754
|
-
}
|
|
24755
|
-
],
|
|
24756
|
-
paths: {
|
|
24757
|
-
"@/*": ["./src/*"]
|
|
24758
|
-
}
|
|
24759
|
-
},
|
|
24760
|
-
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
25585
|
+
compilerOptions: getWebTsConfig(),
|
|
25586
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
|
|
24761
25587
|
exclude: ["node_modules"]
|
|
24762
25588
|
};
|
|
24763
25589
|
await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify(platformTsconfigContent, null, 2));
|
|
@@ -24914,8 +25740,107 @@ Built with \u2764\uFE0F using Bun monorepo features
|
|
|
24914
25740
|
exclude: ["node_modules"]
|
|
24915
25741
|
};
|
|
24916
25742
|
await writeFile(join(projectPath, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
|
|
25743
|
+
if (context.database && context.database !== "none") {
|
|
25744
|
+
const dbPackagePath = join(projectPath, "packages/db");
|
|
25745
|
+
const dbPackageJson = {
|
|
25746
|
+
name: `@${context.packageName}/db`,
|
|
25747
|
+
version: "0.0.0",
|
|
25748
|
+
private: true,
|
|
25749
|
+
main: "./src/index.ts",
|
|
25750
|
+
types: "./src/index.ts",
|
|
25751
|
+
dependencies: context.database === "supabase" ? {
|
|
25752
|
+
"@supabase/supabase-js": "^2.48.1",
|
|
25753
|
+
"drizzle-orm": "^0.38.0",
|
|
25754
|
+
postgres: "^3.4.5"
|
|
25755
|
+
} : {
|
|
25756
|
+
"drizzle-orm": "^0.38.0"
|
|
25757
|
+
},
|
|
25758
|
+
devDependencies: {
|
|
25759
|
+
"drizzle-kit": "^0.30.1",
|
|
25760
|
+
"@types/bun": "latest",
|
|
25761
|
+
typescript: "^5.7.2"
|
|
25762
|
+
}
|
|
25763
|
+
};
|
|
25764
|
+
await writeFile(join(dbPackagePath, "package.json"), JSON.stringify(dbPackageJson, null, 2));
|
|
25765
|
+
if (context.database === "postgres-drizzle") {
|
|
25766
|
+
await setupPostgresDrizzle(dbPackagePath, context, true);
|
|
25767
|
+
} else if (context.database === "supabase") {
|
|
25768
|
+
await setupSupabase(dbPackagePath, context, true);
|
|
25769
|
+
} else if (context.database === "sqlite-drizzle") {
|
|
25770
|
+
await setupSQLiteDrizzle(dbPackagePath, context, true);
|
|
25771
|
+
}
|
|
25772
|
+
const apiPackageJson2 = JSON.parse(await Bun.file(join(projectPath, "apps/api/package.json")).text());
|
|
25773
|
+
apiPackageJson2.dependencies[`@${context.packageName}/db`] = "workspace:*";
|
|
25774
|
+
await writeFile(join(projectPath, "apps/api/package.json"), JSON.stringify(apiPackageJson2, null, 2));
|
|
25775
|
+
}
|
|
25776
|
+
if (context.codeQuality === "ultracite") {
|
|
25777
|
+
await setupUltracite(projectPath, context);
|
|
25778
|
+
} else {
|
|
25779
|
+
await setupBiome(projectPath, context);
|
|
25780
|
+
}
|
|
25781
|
+
if (context.docker) {
|
|
25782
|
+
await setupDocker(projectPath, context);
|
|
25783
|
+
const dockerCompose = `version: '3.8'
|
|
25784
|
+
|
|
25785
|
+
services:
|
|
25786
|
+
web:
|
|
25787
|
+
build:
|
|
25788
|
+
context: ./apps/web
|
|
25789
|
+
dockerfile: ../../Dockerfile
|
|
25790
|
+
ports:
|
|
25791
|
+
- "3000:3000"
|
|
25792
|
+
environment:
|
|
25793
|
+
- NODE_ENV=production
|
|
25794
|
+
${context.database === "supabase" ? "- NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL}\n - NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}" : ""}
|
|
25795
|
+
restart: unless-stopped
|
|
25796
|
+
|
|
25797
|
+
platform:
|
|
25798
|
+
build:
|
|
25799
|
+
context: ./apps/platform
|
|
25800
|
+
dockerfile: ../../Dockerfile
|
|
25801
|
+
ports:
|
|
25802
|
+
- "3001:3000"
|
|
25803
|
+
environment:
|
|
25804
|
+
- NODE_ENV=production
|
|
25805
|
+
${context.database === "supabase" ? "- NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL}\n - NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}" : ""}
|
|
25806
|
+
restart: unless-stopped
|
|
25807
|
+
|
|
25808
|
+
api:
|
|
25809
|
+
build:
|
|
25810
|
+
context: ./apps/api
|
|
25811
|
+
dockerfile: ../../Dockerfile
|
|
25812
|
+
ports:
|
|
25813
|
+
- "3002:3001"
|
|
25814
|
+
environment:
|
|
25815
|
+
- NODE_ENV=production
|
|
25816
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `- DATABASE_URL=\${DATABASE_URL}` : ""}
|
|
25817
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `depends_on:
|
|
25818
|
+
- db` : ""}
|
|
25819
|
+
restart: unless-stopped
|
|
25820
|
+
|
|
25821
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `db:
|
|
25822
|
+
image: ${context.database === "sqlite-drizzle" ? "alpine:latest" : "postgres:16-alpine"}
|
|
25823
|
+
${context.database !== "sqlite-drizzle" ? `environment:
|
|
25824
|
+
- POSTGRES_USER=postgres
|
|
25825
|
+
- POSTGRES_PASSWORD=postgres
|
|
25826
|
+
- POSTGRES_DB=${context.projectName}
|
|
25827
|
+
volumes:
|
|
25828
|
+
- postgres_data:/var/lib/postgresql/data
|
|
25829
|
+
ports:
|
|
25830
|
+
- "5432:5432"` : `volumes:
|
|
25831
|
+
- sqlite_data:/data`}
|
|
25832
|
+
restart: unless-stopped
|
|
25833
|
+
` : ""}
|
|
25834
|
+
${context.database && context.database !== "none" && context.database !== "supabase" ? `volumes:
|
|
25835
|
+
${context.database === "sqlite-drizzle" ? "sqlite_data:" : "postgres_data:"}` : ""}
|
|
25836
|
+
`;
|
|
25837
|
+
await writeFile(join(projectPath, "docker-compose.yml"), dockerCompose);
|
|
25838
|
+
}
|
|
25839
|
+
if (context.cicd) {
|
|
25840
|
+
await setupGitHubActions(projectPath, context);
|
|
25841
|
+
}
|
|
24917
25842
|
}
|
|
24918
|
-
// src/commands/init.
|
|
25843
|
+
// src/commands/init.enhanced.ts
|
|
24919
25844
|
function getOptionValue(envVar, option, defaultValue) {
|
|
24920
25845
|
const envValue = process.env[envVar];
|
|
24921
25846
|
if (envValue !== undefined) {
|
|
@@ -24927,7 +25852,7 @@ function getOptionValue(envVar, option, defaultValue) {
|
|
|
24927
25852
|
}
|
|
24928
25853
|
return option ?? defaultValue;
|
|
24929
25854
|
}
|
|
24930
|
-
async function
|
|
25855
|
+
async function enhancedInitCommand(options = {}) {
|
|
24931
25856
|
const isNonInteractive = process.env.BUNKIT_NON_INTERACTIVE === "true" || options.nonInteractive === true;
|
|
24932
25857
|
let projectName = getOptionValue("BUNKIT_PROJECT_NAME", options.name);
|
|
24933
25858
|
if (!projectName) {
|
|
@@ -24956,7 +25881,7 @@ async function initCommand(options = {}) {
|
|
|
24956
25881
|
let preset = getOptionValue("BUNKIT_PRESET", options.preset);
|
|
24957
25882
|
if (!preset) {
|
|
24958
25883
|
if (isNonInteractive) {
|
|
24959
|
-
throw new Error("Preset is required in non-interactive mode. " + "Provide it via BUNKIT_PRESET env var or --preset flag.
|
|
25884
|
+
throw new Error("Preset is required in non-interactive mode. " + "Provide it via BUNKIT_PRESET env var or --preset flag.");
|
|
24960
25885
|
}
|
|
24961
25886
|
preset = await ve({
|
|
24962
25887
|
message: "\uD83C\uDFA8 Which preset would you like?",
|
|
@@ -24964,22 +25889,22 @@ async function initCommand(options = {}) {
|
|
|
24964
25889
|
{
|
|
24965
25890
|
value: "minimal",
|
|
24966
25891
|
label: "\u26A1 Minimal",
|
|
24967
|
-
hint: "Single
|
|
25892
|
+
hint: "Single file, clean start - perfect for learning Bun"
|
|
24968
25893
|
},
|
|
24969
25894
|
{
|
|
24970
25895
|
value: "web",
|
|
24971
|
-
label: "\uD83C\uDF10 Web",
|
|
24972
|
-
hint: "Next.js 16 + React 19"
|
|
25896
|
+
label: "\uD83C\uDF10 Web App",
|
|
25897
|
+
hint: "Next.js 16 + React 19 - full-stack web application"
|
|
24973
25898
|
},
|
|
24974
25899
|
{
|
|
24975
25900
|
value: "api",
|
|
24976
|
-
label: "\uD83D\uDE80 API",
|
|
24977
|
-
hint: "Hono + Bun.serve()"
|
|
25901
|
+
label: "\uD83D\uDE80 API Server",
|
|
25902
|
+
hint: "Hono + Bun.serve() - ultra-fast REST API"
|
|
24978
25903
|
},
|
|
24979
25904
|
{
|
|
24980
25905
|
value: "full",
|
|
24981
|
-
label: "\uD83D\uDCE6 Full-
|
|
24982
|
-
hint: "
|
|
25906
|
+
label: "\uD83D\uDCE6 Full-Stack Monorepo",
|
|
25907
|
+
hint: "Web + Platform + API - enterprise SaaS setup"
|
|
24983
25908
|
}
|
|
24984
25909
|
]
|
|
24985
25910
|
});
|
|
@@ -24987,17 +25912,221 @@ async function initCommand(options = {}) {
|
|
|
24987
25912
|
xe("Operation cancelled.");
|
|
24988
25913
|
process.exit(0);
|
|
24989
25914
|
}
|
|
24990
|
-
}
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
25915
|
+
}
|
|
25916
|
+
let database = getOptionValue("BUNKIT_DATABASE", options.database);
|
|
25917
|
+
if (!database && (preset === "api" || preset === "full")) {
|
|
25918
|
+
if (!isNonInteractive) {
|
|
25919
|
+
database = await ve({
|
|
25920
|
+
message: "\uD83D\uDDC4\uFE0F Database setup?",
|
|
25921
|
+
options: [
|
|
25922
|
+
{
|
|
25923
|
+
value: "postgres-drizzle",
|
|
25924
|
+
label: "PostgreSQL + Drizzle ORM",
|
|
25925
|
+
hint: "Production-ready, type-safe SQL database"
|
|
25926
|
+
},
|
|
25927
|
+
{
|
|
25928
|
+
value: "supabase",
|
|
25929
|
+
label: "Supabase",
|
|
25930
|
+
hint: "PostgreSQL + Auth + Storage + Realtime - all-in-one"
|
|
25931
|
+
},
|
|
25932
|
+
{
|
|
25933
|
+
value: "sqlite-drizzle",
|
|
25934
|
+
label: "SQLite + Drizzle ORM",
|
|
25935
|
+
hint: "Local-first, embedded database - perfect for prototypes"
|
|
25936
|
+
},
|
|
25937
|
+
{
|
|
25938
|
+
value: "none",
|
|
25939
|
+
label: "None",
|
|
25940
|
+
hint: "I'll add it later"
|
|
25941
|
+
}
|
|
25942
|
+
]
|
|
25943
|
+
});
|
|
25944
|
+
if (pD(database)) {
|
|
25945
|
+
xe("Operation cancelled.");
|
|
25946
|
+
process.exit(0);
|
|
25947
|
+
}
|
|
25948
|
+
} else {
|
|
25949
|
+
database = "none";
|
|
25950
|
+
}
|
|
25951
|
+
}
|
|
25952
|
+
let codeQuality = getOptionValue("BUNKIT_CODE_QUALITY", options.codeQuality, "ultracite");
|
|
25953
|
+
if (!codeQuality) {
|
|
25954
|
+
if (!isNonInteractive) {
|
|
25955
|
+
codeQuality = await ve({
|
|
25956
|
+
message: "\uD83E\uDD16 Code quality setup?",
|
|
25957
|
+
options: [
|
|
25958
|
+
{
|
|
25959
|
+
value: "ultracite",
|
|
25960
|
+
label: "Ultracite (Recommended)",
|
|
25961
|
+
hint: "AI-optimized Biome preset - syncs rules for Cursor, Claude, Windsurf"
|
|
25962
|
+
},
|
|
25963
|
+
{
|
|
25964
|
+
value: "biome",
|
|
25965
|
+
label: "Biome",
|
|
25966
|
+
hint: "Standard Biome with good defaults - fast and reliable"
|
|
25967
|
+
}
|
|
25968
|
+
]
|
|
25969
|
+
});
|
|
25970
|
+
if (pD(codeQuality)) {
|
|
25971
|
+
xe("Operation cancelled.");
|
|
25972
|
+
process.exit(0);
|
|
25973
|
+
}
|
|
25974
|
+
} else {
|
|
25975
|
+
codeQuality = "ultracite";
|
|
25976
|
+
}
|
|
25977
|
+
}
|
|
25978
|
+
let tsStrictness = getOptionValue("BUNKIT_TS_STRICTNESS", options.tsStrictness, "strict");
|
|
25979
|
+
if (!tsStrictness) {
|
|
25980
|
+
if (!isNonInteractive) {
|
|
25981
|
+
tsStrictness = await ve({
|
|
25982
|
+
message: "\uD83D\uDD12 TypeScript strictness?",
|
|
25983
|
+
options: [
|
|
25984
|
+
{
|
|
25985
|
+
value: "strict",
|
|
25986
|
+
label: "Strict (Recommended)",
|
|
25987
|
+
hint: "Maximum type safety - catches bugs early, prevents headaches"
|
|
25988
|
+
},
|
|
25989
|
+
{
|
|
25990
|
+
value: "moderate",
|
|
25991
|
+
label: "Moderate",
|
|
25992
|
+
hint: "Balanced - good safety without being too rigid"
|
|
25993
|
+
},
|
|
25994
|
+
{
|
|
25995
|
+
value: "loose",
|
|
25996
|
+
label: "Loose",
|
|
25997
|
+
hint: "Minimal checks - quick prototyping, migrate from JavaScript"
|
|
25998
|
+
}
|
|
25999
|
+
]
|
|
26000
|
+
});
|
|
26001
|
+
if (pD(tsStrictness)) {
|
|
26002
|
+
xe("Operation cancelled.");
|
|
26003
|
+
process.exit(0);
|
|
26004
|
+
}
|
|
26005
|
+
} else {
|
|
26006
|
+
tsStrictness = "strict";
|
|
26007
|
+
}
|
|
26008
|
+
}
|
|
26009
|
+
let cssFramework = getOptionValue("BUNKIT_CSS_FRAMEWORK", options.cssFramework);
|
|
26010
|
+
if (!cssFramework && (preset === "web" || preset === "full")) {
|
|
26011
|
+
if (!isNonInteractive) {
|
|
26012
|
+
cssFramework = await ve({
|
|
26013
|
+
message: "\uD83C\uDFA8 CSS framework?",
|
|
26014
|
+
options: [
|
|
26015
|
+
{
|
|
26016
|
+
value: "tailwind",
|
|
26017
|
+
label: "Tailwind CSS 4 (Recommended)",
|
|
26018
|
+
hint: "Utility-first, fast, modern - with OKLCH colors and @theme"
|
|
26019
|
+
},
|
|
26020
|
+
{
|
|
26021
|
+
value: "vanilla",
|
|
26022
|
+
label: "Vanilla CSS",
|
|
26023
|
+
hint: "Plain CSS files - full control, no framework"
|
|
26024
|
+
},
|
|
26025
|
+
{
|
|
26026
|
+
value: "css-modules",
|
|
26027
|
+
label: "CSS Modules",
|
|
26028
|
+
hint: "Scoped CSS - automatic class name generation"
|
|
26029
|
+
}
|
|
26030
|
+
]
|
|
26031
|
+
});
|
|
26032
|
+
if (pD(cssFramework)) {
|
|
26033
|
+
xe("Operation cancelled.");
|
|
26034
|
+
process.exit(0);
|
|
26035
|
+
}
|
|
26036
|
+
} else {
|
|
26037
|
+
cssFramework = "tailwind";
|
|
26038
|
+
}
|
|
26039
|
+
}
|
|
26040
|
+
let uiLibrary = getOptionValue("BUNKIT_UI_LIBRARY", options.uiLibrary);
|
|
26041
|
+
if (!uiLibrary && (preset === "web" || preset === "full") && cssFramework === "tailwind") {
|
|
26042
|
+
if (!isNonInteractive) {
|
|
26043
|
+
uiLibrary = await ve({
|
|
26044
|
+
message: "\uD83E\uDDE9 UI component library?",
|
|
26045
|
+
options: [
|
|
26046
|
+
{
|
|
26047
|
+
value: "shadcn",
|
|
26048
|
+
label: "shadcn/ui (Recommended)",
|
|
26049
|
+
hint: "64+ components, accessible, customizable - production-ready"
|
|
26050
|
+
},
|
|
26051
|
+
{
|
|
26052
|
+
value: "none",
|
|
26053
|
+
label: "None",
|
|
26054
|
+
hint: "I'll build my own components"
|
|
26055
|
+
}
|
|
26056
|
+
]
|
|
26057
|
+
});
|
|
26058
|
+
if (pD(uiLibrary)) {
|
|
26059
|
+
xe("Operation cancelled.");
|
|
26060
|
+
process.exit(0);
|
|
26061
|
+
}
|
|
26062
|
+
} else {
|
|
26063
|
+
uiLibrary = "shadcn";
|
|
26064
|
+
}
|
|
26065
|
+
}
|
|
26066
|
+
let testing = getOptionValue("BUNKIT_TESTING", options.testing, "bun-test");
|
|
26067
|
+
if (!testing) {
|
|
26068
|
+
if (!isNonInteractive) {
|
|
26069
|
+
testing = await ve({
|
|
26070
|
+
message: "\uD83E\uDDEA Testing framework?",
|
|
26071
|
+
options: [
|
|
26072
|
+
{
|
|
26073
|
+
value: "bun-test",
|
|
26074
|
+
label: "Bun Test (Recommended)",
|
|
26075
|
+
hint: "Built-in, fast, Jest-compatible - no extra dependencies"
|
|
26076
|
+
},
|
|
26077
|
+
{
|
|
26078
|
+
value: "vitest",
|
|
26079
|
+
label: "Vitest",
|
|
26080
|
+
hint: "Vite-powered, fast, ESM-first - popular in ecosystem"
|
|
26081
|
+
},
|
|
26082
|
+
{
|
|
26083
|
+
value: "none",
|
|
26084
|
+
label: "None",
|
|
26085
|
+
hint: "I'll add testing later"
|
|
26086
|
+
}
|
|
26087
|
+
]
|
|
26088
|
+
});
|
|
26089
|
+
if (pD(testing)) {
|
|
26090
|
+
xe("Operation cancelled.");
|
|
26091
|
+
process.exit(0);
|
|
26092
|
+
}
|
|
26093
|
+
} else {
|
|
26094
|
+
testing = "bun-test";
|
|
26095
|
+
}
|
|
26096
|
+
}
|
|
26097
|
+
let docker = getOptionValue("BUNKIT_DOCKER", options.docker, false);
|
|
26098
|
+
if (docker === undefined) {
|
|
26099
|
+
if (!isNonInteractive) {
|
|
26100
|
+
docker = await ye({
|
|
26101
|
+
message: "\uD83D\uDC33 Add Docker configuration?",
|
|
26102
|
+
initialValue: false
|
|
26103
|
+
});
|
|
26104
|
+
if (pD(docker)) {
|
|
26105
|
+
xe("Operation cancelled.");
|
|
26106
|
+
process.exit(0);
|
|
26107
|
+
}
|
|
26108
|
+
} else {
|
|
26109
|
+
docker = false;
|
|
26110
|
+
}
|
|
26111
|
+
}
|
|
26112
|
+
let cicd = getOptionValue("BUNKIT_CICD", options.cicd, false);
|
|
26113
|
+
if (cicd === undefined) {
|
|
26114
|
+
if (!isNonInteractive) {
|
|
26115
|
+
cicd = await ye({
|
|
26116
|
+
message: "\u2699\uFE0F Add GitHub Actions CI/CD?",
|
|
26117
|
+
initialValue: false
|
|
26118
|
+
});
|
|
26119
|
+
if (pD(cicd)) {
|
|
26120
|
+
xe("Operation cancelled.");
|
|
26121
|
+
process.exit(0);
|
|
26122
|
+
}
|
|
26123
|
+
} else {
|
|
26124
|
+
cicd = false;
|
|
24994
26125
|
}
|
|
24995
26126
|
}
|
|
24996
26127
|
let shouldInstall = getOptionValue("BUNKIT_INSTALL", options.install, true);
|
|
24997
26128
|
if (shouldInstall === undefined) {
|
|
24998
|
-
if (isNonInteractive) {
|
|
24999
|
-
shouldInstall = true;
|
|
25000
|
-
} else {
|
|
26129
|
+
if (!isNonInteractive) {
|
|
25001
26130
|
shouldInstall = await ye({
|
|
25002
26131
|
message: "\uD83D\uDCE5 Install dependencies?",
|
|
25003
26132
|
initialValue: true
|
|
@@ -25006,13 +26135,13 @@ async function initCommand(options = {}) {
|
|
|
25006
26135
|
xe("Operation cancelled.");
|
|
25007
26136
|
process.exit(0);
|
|
25008
26137
|
}
|
|
26138
|
+
} else {
|
|
26139
|
+
shouldInstall = true;
|
|
25009
26140
|
}
|
|
25010
26141
|
}
|
|
25011
26142
|
let shouldInitGit = getOptionValue("BUNKIT_GIT", options.git, true);
|
|
25012
26143
|
if (shouldInitGit === undefined) {
|
|
25013
|
-
if (isNonInteractive) {
|
|
25014
|
-
shouldInitGit = true;
|
|
25015
|
-
} else {
|
|
26144
|
+
if (!isNonInteractive) {
|
|
25016
26145
|
shouldInitGit = await ye({
|
|
25017
26146
|
message: "\uD83D\uDD27 Initialize git repository?",
|
|
25018
26147
|
initialValue: true
|
|
@@ -25021,16 +26150,39 @@ async function initCommand(options = {}) {
|
|
|
25021
26150
|
xe("Operation cancelled.");
|
|
25022
26151
|
process.exit(0);
|
|
25023
26152
|
}
|
|
26153
|
+
} else {
|
|
26154
|
+
shouldInitGit = true;
|
|
25024
26155
|
}
|
|
25025
26156
|
}
|
|
25026
|
-
if (isNonInteractive) {
|
|
26157
|
+
if (!isNonInteractive) {
|
|
25027
26158
|
console.log(`
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
25032
|
-
|
|
25033
|
-
|
|
26159
|
+
` + boxen([
|
|
26160
|
+
`${source_default.bold("Project:")} ${source_default.cyan(projectName)}`,
|
|
26161
|
+
`${source_default.bold("Preset:")} ${source_default.cyan(preset)}`,
|
|
26162
|
+
database ? `${source_default.bold("Database:")} ${source_default.cyan(database)}` : "",
|
|
26163
|
+
`${source_default.bold("Code Quality:")} ${source_default.cyan(codeQuality)}`,
|
|
26164
|
+
`${source_default.bold("TypeScript:")} ${source_default.cyan(tsStrictness)}`,
|
|
26165
|
+
cssFramework ? `${source_default.bold("CSS:")} ${source_default.cyan(cssFramework)}` : "",
|
|
26166
|
+
uiLibrary ? `${source_default.bold("UI Library:")} ${source_default.cyan(uiLibrary)}` : "",
|
|
26167
|
+
`${source_default.bold("Testing:")} ${source_default.cyan(testing)}`,
|
|
26168
|
+
docker ? `${source_default.bold("Docker:")} ${source_default.green("\u2713")}` : "",
|
|
26169
|
+
cicd ? `${source_default.bold("CI/CD:")} ${source_default.green("\u2713")}` : ""
|
|
26170
|
+
].filter(Boolean).join(`
|
|
26171
|
+
`), {
|
|
26172
|
+
padding: 1,
|
|
26173
|
+
title: "\uD83D\uDCCB Configuration",
|
|
26174
|
+
titleAlignment: "left",
|
|
26175
|
+
borderColor: "cyan",
|
|
26176
|
+
borderStyle: "round"
|
|
26177
|
+
}));
|
|
26178
|
+
const confirm = await ye({
|
|
26179
|
+
message: "Proceed with this configuration?",
|
|
26180
|
+
initialValue: true
|
|
26181
|
+
});
|
|
26182
|
+
if (pD(confirm) || !confirm) {
|
|
26183
|
+
xe("Operation cancelled.");
|
|
26184
|
+
process.exit(0);
|
|
26185
|
+
}
|
|
25034
26186
|
}
|
|
25035
26187
|
const s = Y2();
|
|
25036
26188
|
s.start("\uD83D\uDD28 Creating project structure...");
|
|
@@ -25040,7 +26192,17 @@ async function initCommand(options = {}) {
|
|
|
25040
26192
|
preset,
|
|
25041
26193
|
path: projectName,
|
|
25042
26194
|
git: shouldInitGit,
|
|
25043
|
-
install: shouldInstall
|
|
26195
|
+
install: shouldInstall,
|
|
26196
|
+
database,
|
|
26197
|
+
codeQuality,
|
|
26198
|
+
tsStrictness,
|
|
26199
|
+
uiLibrary,
|
|
26200
|
+
cssFramework,
|
|
26201
|
+
testing,
|
|
26202
|
+
docker,
|
|
26203
|
+
cicd,
|
|
26204
|
+
envExample: true,
|
|
26205
|
+
pathAliases: true
|
|
25044
26206
|
};
|
|
25045
26207
|
await createProject(config);
|
|
25046
26208
|
const projectPath = join(process.cwd(), config.path);
|
|
@@ -25060,34 +26222,40 @@ async function initCommand(options = {}) {
|
|
|
25060
26222
|
await buildFullPreset(projectPath, context);
|
|
25061
26223
|
break;
|
|
25062
26224
|
}
|
|
26225
|
+
if (database && database !== "none") {
|
|
26226
|
+
s.message(`\uD83D\uDDC4\uFE0F Setting up ${database}...`);
|
|
26227
|
+
}
|
|
26228
|
+
if (codeQuality === "ultracite") {
|
|
26229
|
+
s.message("\uD83E\uDD16 Configuring Ultracite for AI editors...");
|
|
26230
|
+
}
|
|
26231
|
+
if (docker) {
|
|
26232
|
+
s.message("\uD83D\uDC33 Adding Docker configuration...");
|
|
26233
|
+
}
|
|
26234
|
+
if (cicd) {
|
|
26235
|
+
s.message("\u2699\uFE0F Adding GitHub Actions...");
|
|
26236
|
+
}
|
|
25063
26237
|
s.stop("\u2705 Project created!");
|
|
25064
26238
|
if (shouldInstall) {
|
|
25065
|
-
const
|
|
25066
|
-
if (
|
|
25067
|
-
|
|
25068
|
-
} else {
|
|
25069
|
-
await installDependencies(projectPath);
|
|
26239
|
+
const additionalDeps = [];
|
|
26240
|
+
if (database && database !== "none") {
|
|
26241
|
+
additionalDeps.push(...getDatabaseDependencies(database));
|
|
25070
26242
|
}
|
|
26243
|
+
if (codeQuality) {
|
|
26244
|
+
additionalDeps.push(...getCodeQualityDependencies(codeQuality));
|
|
26245
|
+
}
|
|
26246
|
+
if (testing === "vitest") {
|
|
26247
|
+
additionalDeps.push("vitest", "@vitest/ui");
|
|
26248
|
+
}
|
|
26249
|
+
if (uiLibrary === "shadcn") {
|
|
26250
|
+
additionalDeps.push("class-variance-authority", "clsx", "tailwind-merge");
|
|
26251
|
+
}
|
|
26252
|
+
await installDependencies(projectPath, additionalDeps);
|
|
25071
26253
|
}
|
|
25072
26254
|
const getDevCommand = () => {
|
|
25073
26255
|
if (preset === "full" || preset === "web")
|
|
25074
26256
|
return "bun dev";
|
|
25075
26257
|
return "bun run dev";
|
|
25076
26258
|
};
|
|
25077
|
-
const getPresetEmoji = () => {
|
|
25078
|
-
switch (preset) {
|
|
25079
|
-
case "minimal":
|
|
25080
|
-
return "\u26A1";
|
|
25081
|
-
case "web":
|
|
25082
|
-
return "\uD83C\uDF10";
|
|
25083
|
-
case "api":
|
|
25084
|
-
return "\uD83D\uDE80";
|
|
25085
|
-
case "full":
|
|
25086
|
-
return "\uD83D\uDCE6";
|
|
25087
|
-
default:
|
|
25088
|
-
return "\uD83C\uDF5E";
|
|
25089
|
-
}
|
|
25090
|
-
};
|
|
25091
26259
|
const nextSteps = [
|
|
25092
26260
|
`${source_default.cyan("cd")} ${projectName}`,
|
|
25093
26261
|
shouldInstall ? "" : `${source_default.cyan("bun install")}`,
|
|
@@ -25097,9 +26265,9 @@ async function initCommand(options = {}) {
|
|
|
25097
26265
|
console.log(`
|
|
25098
26266
|
` + boxen(nextSteps, {
|
|
25099
26267
|
padding: 1,
|
|
25100
|
-
title:
|
|
26268
|
+
title: "\uD83D\uDE80 Next steps",
|
|
25101
26269
|
titleAlignment: "left",
|
|
25102
|
-
borderColor: "
|
|
26270
|
+
borderColor: "green",
|
|
25103
26271
|
borderStyle: "round"
|
|
25104
26272
|
}));
|
|
25105
26273
|
} catch (error) {
|
|
@@ -25108,18 +26276,6 @@ async function initCommand(options = {}) {
|
|
|
25108
26276
|
process.exit(1);
|
|
25109
26277
|
}
|
|
25110
26278
|
}
|
|
25111
|
-
function getDependenciesForPreset(preset) {
|
|
25112
|
-
switch (preset) {
|
|
25113
|
-
case "web":
|
|
25114
|
-
return ["react@19.1.0", "react-dom@19.1.0", "next@16.0.0", "tailwindcss@4.1.7"];
|
|
25115
|
-
case "api":
|
|
25116
|
-
return ["hono@4.7.12"];
|
|
25117
|
-
case "full":
|
|
25118
|
-
return [];
|
|
25119
|
-
default:
|
|
25120
|
-
return [];
|
|
25121
|
-
}
|
|
25122
|
-
}
|
|
25123
26279
|
|
|
25124
26280
|
// src/commands/create.ts
|
|
25125
26281
|
async function createCommand2(preset, name, options) {
|
|
@@ -25156,10 +26312,10 @@ var packageJson = await Bun.file(new URL("../package.json", import.meta.url)).js
|
|
|
25156
26312
|
var VERSION = packageJson.version;
|
|
25157
26313
|
var program2 = new Command;
|
|
25158
26314
|
program2.name("bunkit").description("Bake production-ready apps in seconds").version(VERSION);
|
|
25159
|
-
program2.command("init").description("Create a new project
|
|
26315
|
+
program2.command("init").description("Create a new project with full customization").option("--name <name>", "Project name").option("--preset <preset>", "Preset type (minimal, web, api, full)").option("--database <database>", "Database (postgres-drizzle, supabase, sqlite-drizzle, none)").option("--code-quality <quality>", "Code quality (ultracite, biome)").option("--ts-strictness <strictness>", "TypeScript strictness (strict, moderate, loose)").option("--ui-library <library>", "UI library (shadcn, none)").option("--css-framework <framework>", "CSS framework (tailwind, vanilla, css-modules)").option("--testing <framework>", "Testing framework (bun-test, vitest, none)").option("--docker", "Add Docker configuration").option("--cicd", "Add GitHub Actions CI/CD").option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").option("--non-interactive", "Run without prompts (requires all options)").action(async (options) => {
|
|
25160
26316
|
showBanner(VERSION);
|
|
25161
26317
|
try {
|
|
25162
|
-
await
|
|
26318
|
+
await enhancedInitCommand(options);
|
|
25163
26319
|
Se(import_picocolors5.default.green("\u2728 Done! Your project is ready to bake! \uD83C\uDF5E"));
|
|
25164
26320
|
} catch (error) {
|
|
25165
26321
|
M2.error(error.message);
|