bunkit-cli 0.4.2 → 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 +1594 -106
- 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);
|
|
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
|
+
}
|
|
24281
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",
|
|
@@ -24438,7 +25267,8 @@ coverage = true
|
|
|
24438
25267
|
"@types/react": "^19.1.0",
|
|
24439
25268
|
"@types/react-dom": "^19.1.0",
|
|
24440
25269
|
"@types/node": "^22.10.6",
|
|
24441
|
-
typescript: "^5.7.2"
|
|
25270
|
+
typescript: "^5.7.2",
|
|
25271
|
+
tailwindcss: "^4.1.5"
|
|
24442
25272
|
}
|
|
24443
25273
|
};
|
|
24444
25274
|
await writeFile(join(projectPath, "apps/web/package.json"), JSON.stringify(webPackageJson, null, 2));
|
|
@@ -24461,7 +25291,8 @@ coverage = true
|
|
|
24461
25291
|
"@types/react": "^19.1.0",
|
|
24462
25292
|
"@types/react-dom": "^19.1.0",
|
|
24463
25293
|
"@types/node": "^22.10.6",
|
|
24464
|
-
typescript: "^5.7.2"
|
|
25294
|
+
typescript: "^5.7.2",
|
|
25295
|
+
tailwindcss: "^4.1.5"
|
|
24465
25296
|
}
|
|
24466
25297
|
};
|
|
24467
25298
|
await writeFile(join(projectPath, "apps/platform/package.json"), JSON.stringify(platformPackageJson, null, 2));
|
|
@@ -24471,7 +25302,8 @@ coverage = true
|
|
|
24471
25302
|
private: true,
|
|
24472
25303
|
scripts: {
|
|
24473
25304
|
dev: "bun run --hot src/index.ts",
|
|
24474
|
-
start: "bun run src/index.ts"
|
|
25305
|
+
start: "bun run src/index.ts",
|
|
25306
|
+
typecheck: "tsc --noEmit"
|
|
24475
25307
|
},
|
|
24476
25308
|
dependencies: {
|
|
24477
25309
|
hono: "catalog:",
|
|
@@ -24507,6 +25339,332 @@ export interface ApiResponse<T = unknown> {
|
|
|
24507
25339
|
}
|
|
24508
25340
|
`;
|
|
24509
25341
|
await writeFile(join(projectPath, "packages/types/src/index.ts"), typesContent);
|
|
25342
|
+
const utilsPackageJson = {
|
|
25343
|
+
name: `@${context.packageName}/utils`,
|
|
25344
|
+
version: "0.0.0",
|
|
25345
|
+
private: true,
|
|
25346
|
+
main: "./src/index.ts",
|
|
25347
|
+
types: "./src/index.ts"
|
|
25348
|
+
};
|
|
25349
|
+
await writeFile(join(projectPath, "packages/utils/package.json"), JSON.stringify(utilsPackageJson, null, 2));
|
|
25350
|
+
const utilsContent = `// Shared utilities for ${context.projectName}
|
|
25351
|
+
|
|
25352
|
+
export function formatDate(date: Date): string {
|
|
25353
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
25354
|
+
year: 'numeric',
|
|
25355
|
+
month: 'long',
|
|
25356
|
+
day: 'numeric',
|
|
25357
|
+
}).format(date);
|
|
25358
|
+
}
|
|
25359
|
+
|
|
25360
|
+
export function validateEmail(email: string): boolean {
|
|
25361
|
+
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
|
|
25362
|
+
return emailRegex.test(email);
|
|
25363
|
+
}
|
|
25364
|
+
|
|
25365
|
+
export function generateId(): string {
|
|
25366
|
+
return Math.random().toString(36).substring(2, 15);
|
|
25367
|
+
}
|
|
25368
|
+
`;
|
|
25369
|
+
await writeFile(join(projectPath, "packages/utils/src/index.ts"), utilsContent);
|
|
25370
|
+
await ensureDirectory(join(projectPath, "apps/web/src/app"));
|
|
25371
|
+
const webLayoutContent = `import type { Metadata } from 'next';
|
|
25372
|
+
import './globals.css';
|
|
25373
|
+
|
|
25374
|
+
export const metadata: Metadata = {
|
|
25375
|
+
title: '${context.projectName}',
|
|
25376
|
+
description: 'Enterprise SaaS built with bunkit \uD83C\uDF5E',
|
|
25377
|
+
};
|
|
25378
|
+
|
|
25379
|
+
export default function RootLayout({
|
|
25380
|
+
children,
|
|
25381
|
+
}: {
|
|
25382
|
+
children: React.ReactNode;
|
|
25383
|
+
}) {
|
|
25384
|
+
return (
|
|
25385
|
+
<html lang="en">
|
|
25386
|
+
<body>{children}</body>
|
|
25387
|
+
</html>
|
|
25388
|
+
);
|
|
25389
|
+
}
|
|
25390
|
+
`;
|
|
25391
|
+
await writeFile(join(projectPath, "apps/web/src/app/layout.tsx"), webLayoutContent);
|
|
25392
|
+
const webPageContent = `export default function Home() {
|
|
25393
|
+
return (
|
|
25394
|
+
<main className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
25395
|
+
<div className="text-center space-y-6 p-8">
|
|
25396
|
+
<h1 className="text-6xl font-bold text-gray-900">
|
|
25397
|
+
Welcome to ${context.projectName} \uD83C\uDF5E
|
|
25398
|
+
</h1>
|
|
25399
|
+
<p className="text-xl text-gray-600 max-w-2xl">
|
|
25400
|
+
Enterprise-grade SaaS monorepo built with Next.js 16, React 19, Hono, and Bun
|
|
25401
|
+
</p>
|
|
25402
|
+
<div className="flex gap-4 justify-center mt-8">
|
|
25403
|
+
<a
|
|
25404
|
+
href="/dashboard"
|
|
25405
|
+
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
|
|
25406
|
+
>
|
|
25407
|
+
Get Started
|
|
25408
|
+
</a>
|
|
25409
|
+
<a
|
|
25410
|
+
href="/docs"
|
|
25411
|
+
className="px-6 py-3 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition"
|
|
25412
|
+
>
|
|
25413
|
+
Documentation
|
|
25414
|
+
</a>
|
|
25415
|
+
</div>
|
|
25416
|
+
</div>
|
|
25417
|
+
</main>
|
|
25418
|
+
);
|
|
25419
|
+
}
|
|
25420
|
+
`;
|
|
25421
|
+
await writeFile(join(projectPath, "apps/web/src/app/page.tsx"), webPageContent);
|
|
25422
|
+
const webGlobalsCssContent = `@import "tailwindcss";
|
|
25423
|
+
|
|
25424
|
+
@theme {
|
|
25425
|
+
--color-primary: oklch(0.6 0.2 250);
|
|
25426
|
+
--color-secondary: oklch(0.5 0.15 280);
|
|
25427
|
+
}
|
|
25428
|
+
`;
|
|
25429
|
+
await writeFile(join(projectPath, "apps/web/src/app/globals.css"), webGlobalsCssContent);
|
|
25430
|
+
const webNextConfigContent = `import type { NextConfig } from 'next';
|
|
25431
|
+
|
|
25432
|
+
const nextConfig: NextConfig = {
|
|
25433
|
+
/* config options here */
|
|
25434
|
+
};
|
|
25435
|
+
|
|
25436
|
+
export default nextConfig;
|
|
25437
|
+
`;
|
|
25438
|
+
await writeFile(join(projectPath, "apps/web/next.config.ts"), webNextConfigContent);
|
|
25439
|
+
const webTailwindConfigContent = `import type { Config } from 'tailwindcss';
|
|
25440
|
+
|
|
25441
|
+
const config: Config = {
|
|
25442
|
+
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
|
25443
|
+
theme: {
|
|
25444
|
+
extend: {},
|
|
25445
|
+
},
|
|
25446
|
+
plugins: [],
|
|
25447
|
+
};
|
|
25448
|
+
|
|
25449
|
+
export default config;
|
|
25450
|
+
`;
|
|
25451
|
+
await writeFile(join(projectPath, "apps/web/tailwind.config.ts"), webTailwindConfigContent);
|
|
25452
|
+
const getWebTsConfig = () => {
|
|
25453
|
+
const baseOptions = {
|
|
25454
|
+
target: "ES2017",
|
|
25455
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
25456
|
+
allowJs: true,
|
|
25457
|
+
skipLibCheck: true,
|
|
25458
|
+
noEmit: true,
|
|
25459
|
+
esModuleInterop: true,
|
|
25460
|
+
module: "esnext",
|
|
25461
|
+
moduleResolution: "bundler",
|
|
25462
|
+
resolveJsonModule: true,
|
|
25463
|
+
isolatedModules: true,
|
|
25464
|
+
jsx: "react-jsx",
|
|
25465
|
+
incremental: true,
|
|
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"],
|
|
25496
|
+
exclude: ["node_modules"]
|
|
25497
|
+
};
|
|
25498
|
+
await writeFile(join(projectPath, "apps/web/tsconfig.json"), JSON.stringify(webTsconfigContent, null, 2));
|
|
25499
|
+
await ensureDirectory(join(projectPath, "apps/platform/src/app"));
|
|
25500
|
+
const platformLayoutContent = `import type { Metadata } from 'next';
|
|
25501
|
+
import './globals.css';
|
|
25502
|
+
|
|
25503
|
+
export const metadata: Metadata = {
|
|
25504
|
+
title: '${context.projectName} - Admin Dashboard',
|
|
25505
|
+
description: 'Admin dashboard for ${context.projectName}',
|
|
25506
|
+
};
|
|
25507
|
+
|
|
25508
|
+
export default function RootLayout({
|
|
25509
|
+
children,
|
|
25510
|
+
}: {
|
|
25511
|
+
children: React.ReactNode;
|
|
25512
|
+
}) {
|
|
25513
|
+
return (
|
|
25514
|
+
<html lang="en">
|
|
25515
|
+
<body>{children}</body>
|
|
25516
|
+
</html>
|
|
25517
|
+
);
|
|
25518
|
+
}
|
|
25519
|
+
`;
|
|
25520
|
+
await writeFile(join(projectPath, "apps/platform/src/app/layout.tsx"), platformLayoutContent);
|
|
25521
|
+
const platformPageContent = `export default function Dashboard() {
|
|
25522
|
+
return (
|
|
25523
|
+
<main className="min-h-screen bg-gray-50">
|
|
25524
|
+
<div className="max-w-7xl mx-auto py-12 px-4">
|
|
25525
|
+
<header className="mb-8">
|
|
25526
|
+
<h1 className="text-4xl font-bold text-gray-900">
|
|
25527
|
+
Admin Dashboard
|
|
25528
|
+
</h1>
|
|
25529
|
+
<p className="text-gray-600 mt-2">
|
|
25530
|
+
Manage your ${context.projectName} platform
|
|
25531
|
+
</p>
|
|
25532
|
+
</header>
|
|
25533
|
+
|
|
25534
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
25535
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
25536
|
+
<h2 className="text-xl font-semibold mb-2">Users</h2>
|
|
25537
|
+
<p className="text-3xl font-bold text-blue-600">1,234</p>
|
|
25538
|
+
</div>
|
|
25539
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
25540
|
+
<h2 className="text-xl font-semibold mb-2">Revenue</h2>
|
|
25541
|
+
<p className="text-3xl font-bold text-green-600">$12,345</p>
|
|
25542
|
+
</div>
|
|
25543
|
+
<div className="bg-white p-6 rounded-lg shadow">
|
|
25544
|
+
<h2 className="text-xl font-semibold mb-2">Active</h2>
|
|
25545
|
+
<p className="text-3xl font-bold text-purple-600">567</p>
|
|
25546
|
+
</div>
|
|
25547
|
+
</div>
|
|
25548
|
+
</div>
|
|
25549
|
+
</main>
|
|
25550
|
+
);
|
|
25551
|
+
}
|
|
25552
|
+
`;
|
|
25553
|
+
await writeFile(join(projectPath, "apps/platform/src/app/page.tsx"), platformPageContent);
|
|
25554
|
+
const platformGlobalsCssContent = `@import "tailwindcss";
|
|
25555
|
+
|
|
25556
|
+
@theme {
|
|
25557
|
+
--color-primary: oklch(0.6 0.2 250);
|
|
25558
|
+
--color-secondary: oklch(0.5 0.15 280);
|
|
25559
|
+
}
|
|
25560
|
+
`;
|
|
25561
|
+
await writeFile(join(projectPath, "apps/platform/src/app/globals.css"), platformGlobalsCssContent);
|
|
25562
|
+
const platformNextConfigContent = `import type { NextConfig } from 'next';
|
|
25563
|
+
|
|
25564
|
+
const nextConfig: NextConfig = {
|
|
25565
|
+
/* config options here */
|
|
25566
|
+
};
|
|
25567
|
+
|
|
25568
|
+
export default nextConfig;
|
|
25569
|
+
`;
|
|
25570
|
+
await writeFile(join(projectPath, "apps/platform/next.config.ts"), platformNextConfigContent);
|
|
25571
|
+
const platformTailwindConfigContent = `import type { Config } from 'tailwindcss';
|
|
25572
|
+
|
|
25573
|
+
const config: Config = {
|
|
25574
|
+
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
|
25575
|
+
theme: {
|
|
25576
|
+
extend: {},
|
|
25577
|
+
},
|
|
25578
|
+
plugins: [],
|
|
25579
|
+
};
|
|
25580
|
+
|
|
25581
|
+
export default config;
|
|
25582
|
+
`;
|
|
25583
|
+
await writeFile(join(projectPath, "apps/platform/tailwind.config.ts"), platformTailwindConfigContent);
|
|
25584
|
+
const platformTsconfigContent = {
|
|
25585
|
+
compilerOptions: getWebTsConfig(),
|
|
25586
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/dev/types/**/*.ts"],
|
|
25587
|
+
exclude: ["node_modules"]
|
|
25588
|
+
};
|
|
25589
|
+
await writeFile(join(projectPath, "apps/platform/tsconfig.json"), JSON.stringify(platformTsconfigContent, null, 2));
|
|
25590
|
+
await ensureDirectory(join(projectPath, "apps/api/src"));
|
|
25591
|
+
const apiIndexContent = `import { Hono } from 'hono';
|
|
25592
|
+
import { serve } from 'bun';
|
|
25593
|
+
import { logger } from 'hono/logger';
|
|
25594
|
+
import { cors } from 'hono/cors';
|
|
25595
|
+
|
|
25596
|
+
const app = new Hono();
|
|
25597
|
+
|
|
25598
|
+
// Middleware
|
|
25599
|
+
app.use('*', logger());
|
|
25600
|
+
app.use('*', cors());
|
|
25601
|
+
|
|
25602
|
+
// Routes
|
|
25603
|
+
app.get('/', (context) => {
|
|
25604
|
+
return context.json({
|
|
25605
|
+
message: 'Welcome to ${context.projectName} API \uD83C\uDF5E',
|
|
25606
|
+
version: '1.0.0',
|
|
25607
|
+
});
|
|
25608
|
+
});
|
|
25609
|
+
|
|
25610
|
+
app.get('/health', (context) => {
|
|
25611
|
+
return context.json({
|
|
25612
|
+
status: 'ok',
|
|
25613
|
+
timestamp: new Date().toISOString(),
|
|
25614
|
+
});
|
|
25615
|
+
});
|
|
25616
|
+
|
|
25617
|
+
app.get('/api/users', (context) => {
|
|
25618
|
+
return context.json({
|
|
25619
|
+
users: [
|
|
25620
|
+
{ id: 1, email: 'john@example.com', name: 'John Doe' },
|
|
25621
|
+
{ id: 2, email: 'jane@example.com', name: 'Jane Smith' },
|
|
25622
|
+
],
|
|
25623
|
+
});
|
|
25624
|
+
});
|
|
25625
|
+
|
|
25626
|
+
// 404 handler
|
|
25627
|
+
app.notFound((context) => {
|
|
25628
|
+
return context.json({ error: 'Not found' }, 404);
|
|
25629
|
+
});
|
|
25630
|
+
|
|
25631
|
+
// Error handler
|
|
25632
|
+
app.onError((error, context) => {
|
|
25633
|
+
console.error(\`Error: \${error}\`);
|
|
25634
|
+
return context.json({ error: 'Internal server error' }, 500);
|
|
25635
|
+
});
|
|
25636
|
+
|
|
25637
|
+
// Start server
|
|
25638
|
+
serve({
|
|
25639
|
+
fetch: app.fetch,
|
|
25640
|
+
port: 3001,
|
|
25641
|
+
development: {
|
|
25642
|
+
hmr: true,
|
|
25643
|
+
console: true,
|
|
25644
|
+
},
|
|
25645
|
+
});
|
|
25646
|
+
|
|
25647
|
+
console.log('\uD83D\uDE80 ${context.projectName} API running on http://localhost:3001');
|
|
25648
|
+
`;
|
|
25649
|
+
await writeFile(join(projectPath, "apps/api/src/index.ts"), apiIndexContent);
|
|
25650
|
+
const apiTsconfigContent = {
|
|
25651
|
+
compilerOptions: {
|
|
25652
|
+
lib: ["ESNext"],
|
|
25653
|
+
target: "ESNext",
|
|
25654
|
+
module: "ESNext",
|
|
25655
|
+
moduleDetection: "force",
|
|
25656
|
+
allowJs: true,
|
|
25657
|
+
moduleResolution: "bundler",
|
|
25658
|
+
allowImportingTsExtensions: true,
|
|
25659
|
+
verbatimModuleSyntax: true,
|
|
25660
|
+
noEmit: true,
|
|
25661
|
+
strict: true,
|
|
25662
|
+
skipLibCheck: true,
|
|
25663
|
+
noFallthroughCasesInSwitch: true,
|
|
25664
|
+
types: ["@types/bun"]
|
|
25665
|
+
}
|
|
25666
|
+
};
|
|
25667
|
+
await writeFile(join(projectPath, "apps/api/tsconfig.json"), JSON.stringify(apiTsconfigContent, null, 2));
|
|
24510
25668
|
const readmeContent = `# ${context.projectName}
|
|
24511
25669
|
|
|
24512
25670
|
Enterprise-grade SaaS monorepo created with [bunkit](https://github.com/Arakiss/bunkit) \uD83C\uDF5E
|
|
@@ -24582,8 +25740,107 @@ Built with \u2764\uFE0F using Bun monorepo features
|
|
|
24582
25740
|
exclude: ["node_modules"]
|
|
24583
25741
|
};
|
|
24584
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
|
+
}
|
|
24585
25842
|
}
|
|
24586
|
-
// src/commands/init.
|
|
25843
|
+
// src/commands/init.enhanced.ts
|
|
24587
25844
|
function getOptionValue(envVar, option, defaultValue) {
|
|
24588
25845
|
const envValue = process.env[envVar];
|
|
24589
25846
|
if (envValue !== undefined) {
|
|
@@ -24595,7 +25852,7 @@ function getOptionValue(envVar, option, defaultValue) {
|
|
|
24595
25852
|
}
|
|
24596
25853
|
return option ?? defaultValue;
|
|
24597
25854
|
}
|
|
24598
|
-
async function
|
|
25855
|
+
async function enhancedInitCommand(options = {}) {
|
|
24599
25856
|
const isNonInteractive = process.env.BUNKIT_NON_INTERACTIVE === "true" || options.nonInteractive === true;
|
|
24600
25857
|
let projectName = getOptionValue("BUNKIT_PROJECT_NAME", options.name);
|
|
24601
25858
|
if (!projectName) {
|
|
@@ -24624,7 +25881,7 @@ async function initCommand(options = {}) {
|
|
|
24624
25881
|
let preset = getOptionValue("BUNKIT_PRESET", options.preset);
|
|
24625
25882
|
if (!preset) {
|
|
24626
25883
|
if (isNonInteractive) {
|
|
24627
|
-
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.");
|
|
24628
25885
|
}
|
|
24629
25886
|
preset = await ve({
|
|
24630
25887
|
message: "\uD83C\uDFA8 Which preset would you like?",
|
|
@@ -24632,22 +25889,22 @@ async function initCommand(options = {}) {
|
|
|
24632
25889
|
{
|
|
24633
25890
|
value: "minimal",
|
|
24634
25891
|
label: "\u26A1 Minimal",
|
|
24635
|
-
hint: "Single
|
|
25892
|
+
hint: "Single file, clean start - perfect for learning Bun"
|
|
24636
25893
|
},
|
|
24637
25894
|
{
|
|
24638
25895
|
value: "web",
|
|
24639
|
-
label: "\uD83C\uDF10 Web",
|
|
24640
|
-
hint: "Next.js 16 + React 19"
|
|
25896
|
+
label: "\uD83C\uDF10 Web App",
|
|
25897
|
+
hint: "Next.js 16 + React 19 - full-stack web application"
|
|
24641
25898
|
},
|
|
24642
25899
|
{
|
|
24643
25900
|
value: "api",
|
|
24644
|
-
label: "\uD83D\uDE80 API",
|
|
24645
|
-
hint: "Hono + Bun.serve()"
|
|
25901
|
+
label: "\uD83D\uDE80 API Server",
|
|
25902
|
+
hint: "Hono + Bun.serve() - ultra-fast REST API"
|
|
24646
25903
|
},
|
|
24647
25904
|
{
|
|
24648
25905
|
value: "full",
|
|
24649
|
-
label: "\uD83D\uDCE6 Full-
|
|
24650
|
-
hint: "
|
|
25906
|
+
label: "\uD83D\uDCE6 Full-Stack Monorepo",
|
|
25907
|
+
hint: "Web + Platform + API - enterprise SaaS setup"
|
|
24651
25908
|
}
|
|
24652
25909
|
]
|
|
24653
25910
|
});
|
|
@@ -24655,17 +25912,221 @@ async function initCommand(options = {}) {
|
|
|
24655
25912
|
xe("Operation cancelled.");
|
|
24656
25913
|
process.exit(0);
|
|
24657
25914
|
}
|
|
24658
|
-
}
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
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;
|
|
24662
26125
|
}
|
|
24663
26126
|
}
|
|
24664
26127
|
let shouldInstall = getOptionValue("BUNKIT_INSTALL", options.install, true);
|
|
24665
26128
|
if (shouldInstall === undefined) {
|
|
24666
|
-
if (isNonInteractive) {
|
|
24667
|
-
shouldInstall = true;
|
|
24668
|
-
} else {
|
|
26129
|
+
if (!isNonInteractive) {
|
|
24669
26130
|
shouldInstall = await ye({
|
|
24670
26131
|
message: "\uD83D\uDCE5 Install dependencies?",
|
|
24671
26132
|
initialValue: true
|
|
@@ -24674,13 +26135,13 @@ async function initCommand(options = {}) {
|
|
|
24674
26135
|
xe("Operation cancelled.");
|
|
24675
26136
|
process.exit(0);
|
|
24676
26137
|
}
|
|
26138
|
+
} else {
|
|
26139
|
+
shouldInstall = true;
|
|
24677
26140
|
}
|
|
24678
26141
|
}
|
|
24679
26142
|
let shouldInitGit = getOptionValue("BUNKIT_GIT", options.git, true);
|
|
24680
26143
|
if (shouldInitGit === undefined) {
|
|
24681
|
-
if (isNonInteractive) {
|
|
24682
|
-
shouldInitGit = true;
|
|
24683
|
-
} else {
|
|
26144
|
+
if (!isNonInteractive) {
|
|
24684
26145
|
shouldInitGit = await ye({
|
|
24685
26146
|
message: "\uD83D\uDD27 Initialize git repository?",
|
|
24686
26147
|
initialValue: true
|
|
@@ -24689,16 +26150,39 @@ async function initCommand(options = {}) {
|
|
|
24689
26150
|
xe("Operation cancelled.");
|
|
24690
26151
|
process.exit(0);
|
|
24691
26152
|
}
|
|
26153
|
+
} else {
|
|
26154
|
+
shouldInitGit = true;
|
|
24692
26155
|
}
|
|
24693
26156
|
}
|
|
24694
|
-
if (isNonInteractive) {
|
|
26157
|
+
if (!isNonInteractive) {
|
|
24695
26158
|
console.log(`
|
|
24696
|
-
|
|
24697
|
-
|
|
24698
|
-
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
|
|
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
|
+
}
|
|
24702
26186
|
}
|
|
24703
26187
|
const s = Y2();
|
|
24704
26188
|
s.start("\uD83D\uDD28 Creating project structure...");
|
|
@@ -24708,7 +26192,17 @@ async function initCommand(options = {}) {
|
|
|
24708
26192
|
preset,
|
|
24709
26193
|
path: projectName,
|
|
24710
26194
|
git: shouldInitGit,
|
|
24711
|
-
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
|
|
24712
26206
|
};
|
|
24713
26207
|
await createProject(config);
|
|
24714
26208
|
const projectPath = join(process.cwd(), config.path);
|
|
@@ -24728,34 +26222,40 @@ async function initCommand(options = {}) {
|
|
|
24728
26222
|
await buildFullPreset(projectPath, context);
|
|
24729
26223
|
break;
|
|
24730
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
|
+
}
|
|
24731
26237
|
s.stop("\u2705 Project created!");
|
|
24732
26238
|
if (shouldInstall) {
|
|
24733
|
-
const
|
|
24734
|
-
if (
|
|
24735
|
-
|
|
24736
|
-
}
|
|
24737
|
-
|
|
26239
|
+
const additionalDeps = [];
|
|
26240
|
+
if (database && database !== "none") {
|
|
26241
|
+
additionalDeps.push(...getDatabaseDependencies(database));
|
|
26242
|
+
}
|
|
26243
|
+
if (codeQuality) {
|
|
26244
|
+
additionalDeps.push(...getCodeQualityDependencies(codeQuality));
|
|
24738
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);
|
|
24739
26253
|
}
|
|
24740
26254
|
const getDevCommand = () => {
|
|
24741
26255
|
if (preset === "full" || preset === "web")
|
|
24742
26256
|
return "bun dev";
|
|
24743
26257
|
return "bun run dev";
|
|
24744
26258
|
};
|
|
24745
|
-
const getPresetEmoji = () => {
|
|
24746
|
-
switch (preset) {
|
|
24747
|
-
case "minimal":
|
|
24748
|
-
return "\u26A1";
|
|
24749
|
-
case "web":
|
|
24750
|
-
return "\uD83C\uDF10";
|
|
24751
|
-
case "api":
|
|
24752
|
-
return "\uD83D\uDE80";
|
|
24753
|
-
case "full":
|
|
24754
|
-
return "\uD83D\uDCE6";
|
|
24755
|
-
default:
|
|
24756
|
-
return "\uD83C\uDF5E";
|
|
24757
|
-
}
|
|
24758
|
-
};
|
|
24759
26259
|
const nextSteps = [
|
|
24760
26260
|
`${source_default.cyan("cd")} ${projectName}`,
|
|
24761
26261
|
shouldInstall ? "" : `${source_default.cyan("bun install")}`,
|
|
@@ -24765,9 +26265,9 @@ async function initCommand(options = {}) {
|
|
|
24765
26265
|
console.log(`
|
|
24766
26266
|
` + boxen(nextSteps, {
|
|
24767
26267
|
padding: 1,
|
|
24768
|
-
title:
|
|
26268
|
+
title: "\uD83D\uDE80 Next steps",
|
|
24769
26269
|
titleAlignment: "left",
|
|
24770
|
-
borderColor: "
|
|
26270
|
+
borderColor: "green",
|
|
24771
26271
|
borderStyle: "round"
|
|
24772
26272
|
}));
|
|
24773
26273
|
} catch (error) {
|
|
@@ -24776,18 +26276,6 @@ async function initCommand(options = {}) {
|
|
|
24776
26276
|
process.exit(1);
|
|
24777
26277
|
}
|
|
24778
26278
|
}
|
|
24779
|
-
function getDependenciesForPreset(preset) {
|
|
24780
|
-
switch (preset) {
|
|
24781
|
-
case "web":
|
|
24782
|
-
return ["react@19.1.0", "react-dom@19.1.0", "next@16.0.0", "tailwindcss@4.1.7"];
|
|
24783
|
-
case "api":
|
|
24784
|
-
return ["hono@4.7.12"];
|
|
24785
|
-
case "full":
|
|
24786
|
-
return [];
|
|
24787
|
-
default:
|
|
24788
|
-
return [];
|
|
24789
|
-
}
|
|
24790
|
-
}
|
|
24791
26279
|
|
|
24792
26280
|
// src/commands/create.ts
|
|
24793
26281
|
async function createCommand2(preset, name, options) {
|
|
@@ -24824,10 +26312,10 @@ var packageJson = await Bun.file(new URL("../package.json", import.meta.url)).js
|
|
|
24824
26312
|
var VERSION = packageJson.version;
|
|
24825
26313
|
var program2 = new Command;
|
|
24826
26314
|
program2.name("bunkit").description("Bake production-ready apps in seconds").version(VERSION);
|
|
24827
|
-
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) => {
|
|
24828
26316
|
showBanner(VERSION);
|
|
24829
26317
|
try {
|
|
24830
|
-
await
|
|
26318
|
+
await enhancedInitCommand(options);
|
|
24831
26319
|
Se(import_picocolors5.default.green("\u2728 Done! Your project is ready to bake! \uD83C\uDF5E"));
|
|
24832
26320
|
} catch (error) {
|
|
24833
26321
|
M2.error(error.message);
|