deploy-bbc 0.0.1
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/CLAUDE.md +329 -0
- package/README.md +0 -0
- package/cli/README.md +0 -0
- package/cli/package.json +43 -0
- package/cli/src/cli/index.ts +449 -0
- package/cli/src/helpers/create-project.ts +66 -0
- package/cli/src/helpers/generate-docker-compose.ts +133 -0
- package/cli/src/helpers/generate-dockerfile.ts +45 -0
- package/cli/src/helpers/init-git.ts +33 -0
- package/cli/src/helpers/install-dependencies.ts +28 -0
- package/cli/src/helpers/log-next-steps.ts +84 -0
- package/cli/src/helpers/scaffold-project.ts +62 -0
- package/cli/src/index.ts +18 -0
- package/cli/src/installers/ai.ts +123 -0
- package/cli/src/installers/auth.ts +132 -0
- package/cli/src/installers/base.ts +16 -0
- package/cli/src/installers/cloud.ts +127 -0
- package/cli/src/installers/database.ts +212 -0
- package/cli/src/installers/docs.ts +93 -0
- package/cli/src/installers/email.ts +119 -0
- package/cli/src/installers/env-variables.ts +27 -0
- package/cli/src/installers/index.ts +145 -0
- package/cli/src/installers/observability.ts +103 -0
- package/cli/src/installers/queue.ts +103 -0
- package/cli/src/installers/ratelimit.ts +98 -0
- package/cli/src/installers/realtime.ts +79 -0
- package/cli/src/installers/testing.ts +88 -0
- package/cli/src/installers/validation.ts +85 -0
- package/cli/src/templates/base/.env.example +3 -0
- package/cli/src/templates/base/README.md +31 -0
- package/cli/src/templates/base/package.json +20 -0
- package/cli/src/templates/base/src/config/index.ts +8 -0
- package/cli/src/templates/base/src/index.ts +26 -0
- package/cli/src/templates/base/src/middleware/error.ts +13 -0
- package/cli/src/templates/base/src/middleware/logger.ts +8 -0
- package/cli/src/templates/base/src/routes/index.ts +12 -0
- package/cli/src/templates/base/src/types/index.ts +2 -0
- package/cli/src/templates/base/src/utils/env.ts +5 -0
- package/cli/src/templates/base/tsconfig.json +20 -0
- package/cli/src/templates/base-bun-native/.env.example +3 -0
- package/cli/src/templates/base-bun-native/README.md +31 -0
- package/cli/src/templates/base-bun-native/package.json +19 -0
- package/cli/src/templates/base-bun-native/src/config/index.ts +8 -0
- package/cli/src/templates/base-bun-native/src/index.ts +50 -0
- package/cli/src/templates/base-bun-native/src/middleware/error.ts +20 -0
- package/cli/src/templates/base-bun-native/src/middleware/logger.ts +6 -0
- package/cli/src/templates/base-bun-native/src/routes/index.ts +21 -0
- package/cli/src/templates/base-bun-native/src/types/index.ts +2 -0
- package/cli/src/templates/base-bun-native/src/utils/env.ts +5 -0
- package/cli/src/templates/base-bun-native/tsconfig.json +20 -0
- package/cli/src/templates/base-express/.env.example +3 -0
- package/cli/src/templates/base-express/README.md +31 -0
- package/cli/src/templates/base-express/package.json +21 -0
- package/cli/src/templates/base-express/src/config/index.ts +8 -0
- package/cli/src/templates/base-express/src/index.ts +27 -0
- package/cli/src/templates/base-express/src/middleware/error.ts +15 -0
- package/cli/src/templates/base-express/src/middleware/logger.ts +12 -0
- package/cli/src/templates/base-express/src/routes/index.ts +12 -0
- package/cli/src/templates/base-express/src/types/index.ts +2 -0
- package/cli/src/templates/base-express/src/utils/env.ts +5 -0
- package/cli/src/templates/base-express/tsconfig.json +20 -0
- package/cli/src/templates/extras/ai/anthropic/src/routes/ai/claude.ts +0 -0
- package/cli/src/templates/extras/ai/anthropic/src/services/ai/anthropic.ts +0 -0
- package/cli/src/templates/extras/ai/gemini/src/services/ai/gemini.ts +0 -0
- package/cli/src/templates/extras/ai/openai/src/routes/ai/chat.ts +0 -0
- package/cli/src/templates/extras/ai/openai/src/services/ai/openai.ts +0 -0
- package/cli/src/templates/extras/ai/vercel-ai/src/routes/ai/generate.ts +0 -0
- package/cli/src/templates/extras/ai/vercel-ai/src/routes/ai/stream.ts +0 -0
- package/cli/src/templates/extras/ai/vercel-ai/src/services/ai/index.ts +0 -0
- package/cli/src/templates/extras/auth/jwt/src/middleware/auth.ts +0 -0
- package/cli/src/templates/extras/auth/jwt/src/routes/auth.ts +0 -0
- package/cli/src/templates/extras/auth/jwt/src/utils/jwt.ts +0 -0
- package/cli/src/templates/extras/auth/oauth/src/config/oauth.ts +0 -0
- package/cli/src/templates/extras/auth/oauth/src/routes/auth.ts +0 -0
- package/cli/src/templates/extras/auth/session/src/config/session.ts +0 -0
- package/cli/src/templates/extras/auth/session/src/middleware/session.ts +0 -0
- package/cli/src/templates/extras/cloud/aws/src/services/aws/s3.ts +0 -0
- package/cli/src/templates/extras/cloud/aws/src/services/aws/ses.ts +0 -0
- package/cli/src/templates/extras/cloud/azure/src/services/azure/blob.ts +0 -0
- package/cli/src/templates/extras/cloud/cloudflare-r2/src/services/cloudflare/r2.ts +0 -0
- package/cli/src/templates/extras/cloud/gcp/src/services/gcp/storage.ts +0 -0
- package/cli/src/templates/extras/database/mongodb/src/db/index.ts +0 -0
- package/cli/src/templates/extras/database/mongodb/src/db/models/user.model.ts +0 -0
- package/cli/src/templates/extras/database/mysql/drizzle.config.ts +0 -0
- package/cli/src/templates/extras/database/postgres/src/db/index.ts +0 -0
- package/cli/src/templates/extras/database/redis/src/db/redis.ts +0 -0
- package/cli/src/templates/extras/docs/scalar/src/openapi/index.ts +0 -0
- package/cli/src/templates/extras/docs/scalar/src/routes/docs.ts +0 -0
- package/cli/src/templates/extras/docs/swagger/src/openapi/index.ts +0 -0
- package/cli/src/templates/extras/docs/swagger/src/routes/docs.ts +0 -0
- package/cli/src/templates/extras/email/nodemailer/src/services/email/nodemailer.ts +0 -0
- package/cli/src/templates/extras/email/resend/src/services/email/resend.ts +0 -0
- package/cli/src/templates/extras/email/resend/src/templates/email/welcome.ts +0 -0
- package/cli/src/templates/extras/email/sendgrid/src/services/email/sendgrid.ts +0 -0
- package/cli/src/templates/extras/observability/logtail/src/config/logger.ts +0 -0
- package/cli/src/templates/extras/observability/sentry/src/config/sentry.ts +0 -0
- package/cli/src/templates/extras/observability/sentry/src/middleware/sentry.ts +0 -0
- package/cli/src/templates/extras/queue/bullmq/src/queue/index.ts +0 -0
- package/cli/src/templates/extras/queue/bullmq/src/queue/jobs/email.job.ts +0 -0
- package/cli/src/templates/extras/queue/bullmq/src/queue/processors/email.processor.ts +0 -0
- package/cli/src/templates/extras/queue/bullmq/src/routes/queue.ts +0 -0
- package/cli/src/templates/extras/queue/inngest/src/inngest/client.ts +0 -0
- package/cli/src/templates/extras/queue/inngest/src/inngest/functions/email.ts +0 -0
- package/cli/src/templates/extras/queue/inngest/src/routes/inngest.ts +0 -0
- package/cli/src/templates/extras/realtime/socketio/src/socket/handlers.ts +0 -0
- package/cli/src/templates/extras/realtime/socketio/src/socket/index.ts +0 -0
- package/cli/src/templates/extras/realtime/sse/src/routes/sse.ts +0 -0
- package/cli/src/templates/extras/testing/vitest/src/__tests__/example.test.ts +0 -0
- package/cli/src/templates/extras/testing/vitest/src/__tests__/setup.ts +0 -0
- package/cli/src/templates/extras/testing/vitest/vitest.config.ts +0 -0
- package/cli/src/templates/extras/validation/yup/src/middleware/index.ts +1 -0
- package/cli/src/templates/extras/validation/yup/src/middleware/validate.ts +83 -0
- package/cli/src/templates/extras/validation/yup/src/routes/users.ts +132 -0
- package/cli/src/templates/extras/validation/zod/src/middleware/index.ts +1 -0
- package/cli/src/templates/extras/validation/zod/src/middleware/validate.ts +80 -0
- package/cli/src/templates/extras/validation/zod/src/routes/users.ts +128 -0
- package/cli/src/types/index.ts +126 -0
- package/cli/src/utils/add-package-dependency.ts +56 -0
- package/cli/src/utils/dependency-version-map.ts +85 -0
- package/cli/src/utils/logger.ts +19 -0
- package/cli/src/utils/parse-name-and-path.ts +55 -0
- package/cli/src/utils/render-title.ts +11 -0
- package/cli/tsconfig.json +35 -0
- package/package.json +20 -0
- package/prettier.config.mjs +0 -0
- package/test-cli.sh +56 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { type CliFlags, type CliResults, AvailablePackages } from "../types/index.js";
|
|
5
|
+
|
|
6
|
+
const defaultOptions: CliFlags = {
|
|
7
|
+
noGit: false,
|
|
8
|
+
noInstall: false,
|
|
9
|
+
default: false,
|
|
10
|
+
CI: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const run_cli = async (): Promise<CliResults> => {
|
|
14
|
+
const program = new Command()
|
|
15
|
+
.name("deploy-bbc")
|
|
16
|
+
.description("Bootstrap a production-ready backend with Bun")
|
|
17
|
+
.argument("[dir]", "The name of the application")
|
|
18
|
+
.option("--noGit", "Skip git initialization", defaultOptions.noGit)
|
|
19
|
+
.option("--noInstall", "Skip dependency installation", defaultOptions.noInstall)
|
|
20
|
+
.option("--default", "Use default options", defaultOptions.default)
|
|
21
|
+
// CI Mode
|
|
22
|
+
.option("--CI", "Run in CI mode (non-interactive)", defaultOptions.CI)
|
|
23
|
+
// Framework options
|
|
24
|
+
.option("--hono", "Use Hono framework (default)")
|
|
25
|
+
.option("--express", "Use Express framework")
|
|
26
|
+
.option("--bun-native", "Use Bun native HTTP server")
|
|
27
|
+
// Database options
|
|
28
|
+
.option("--postgres", "Include PostgreSQL")
|
|
29
|
+
.option("--mysql", "Include MySQL")
|
|
30
|
+
.option("--mongodb", "Include MongoDB")
|
|
31
|
+
.option("--redis", "Include Redis")
|
|
32
|
+
// Auth options
|
|
33
|
+
.option("--jwt", "Include JWT authentication")
|
|
34
|
+
.option("--oauth", "Include OAuth 2.0")
|
|
35
|
+
.option("--session", "Include session-based auth")
|
|
36
|
+
// AI options
|
|
37
|
+
.option("--openai", "Include OpenAI SDK")
|
|
38
|
+
.option("--anthropic", "Include Anthropic Claude SDK")
|
|
39
|
+
.option("--gemini", "Include Google Gemini SDK")
|
|
40
|
+
.option("--vercelAI", "Include Vercel AI SDK")
|
|
41
|
+
// Cloud options
|
|
42
|
+
.option("--aws", "Include AWS SDK")
|
|
43
|
+
.option("--gcp", "Include Google Cloud SDK")
|
|
44
|
+
.option("--azure", "Include Azure SDK")
|
|
45
|
+
.option("--cloudflareR2", "Include Cloudflare R2")
|
|
46
|
+
// Communication options
|
|
47
|
+
.option("--resend", "Include Resend email service")
|
|
48
|
+
.option("--sendgrid", "Include SendGrid")
|
|
49
|
+
.option("--nodemailer", "Include NodeMailer")
|
|
50
|
+
.option("--socketio", "Include Socket.io")
|
|
51
|
+
.option("--sse", "Include Server-Sent Events")
|
|
52
|
+
// Infrastructure options
|
|
53
|
+
.option("--bullmq", "Include BullMQ")
|
|
54
|
+
.option("--inngest", "Include Inngest")
|
|
55
|
+
.option("--upstashRateLimit", "Include Upstash Rate Limit")
|
|
56
|
+
.option("--customRateLimit", "Include custom rate limiting")
|
|
57
|
+
.option("--sentry", "Include Sentry")
|
|
58
|
+
.option("--logtail", "Include LogTail")
|
|
59
|
+
// DevX options
|
|
60
|
+
.option("--swagger", "Include Swagger/OpenAPI")
|
|
61
|
+
.option("--scalar", "Include Scalar API docs")
|
|
62
|
+
.option("--vitest", "Include Vitest testing")
|
|
63
|
+
// Validation options
|
|
64
|
+
.option("--zod", "Include Zod validation")
|
|
65
|
+
.option("--yup", "Include Yup validation")
|
|
66
|
+
.parse(process.argv);
|
|
67
|
+
|
|
68
|
+
const cliProvidedName = program.args[0];
|
|
69
|
+
const cliOptions = program.opts<CliFlags>();
|
|
70
|
+
|
|
71
|
+
// CI Mode - skip interactive prompts
|
|
72
|
+
if (cliOptions.CI) {
|
|
73
|
+
return build_from_flags(cliProvidedName, cliOptions);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Interactive Mode
|
|
77
|
+
const intro = `
|
|
78
|
+
╔═══════════════════════════════════════════════╗
|
|
79
|
+
║ ║
|
|
80
|
+
║ 🚀 deploy-bbc ║
|
|
81
|
+
║ (Best Backend Code) ║
|
|
82
|
+
║ ║
|
|
83
|
+
║ Bootstrap a production-ready backend ║
|
|
84
|
+
║ with Bun, TypeScript & Docker ║
|
|
85
|
+
║ ║
|
|
86
|
+
╚═══════════════════════════════════════════════╝
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
console.log(chalk.cyan(intro));
|
|
90
|
+
|
|
91
|
+
p.intro(chalk.bgCyan(chalk.black(" deploy-bbc ")));
|
|
92
|
+
|
|
93
|
+
const project = await p.group(
|
|
94
|
+
{
|
|
95
|
+
// ──────────────────────────────────────────
|
|
96
|
+
// 📦 CORE
|
|
97
|
+
// ──────────────────────────────────────────
|
|
98
|
+
...add_cli_header("📦 CORE"),
|
|
99
|
+
|
|
100
|
+
projectName: () =>
|
|
101
|
+
p.text({
|
|
102
|
+
message: "What will your project be called?",
|
|
103
|
+
placeholder: "my-awesome-api",
|
|
104
|
+
defaultValue: cliProvidedName || "my-backend",
|
|
105
|
+
validate: (value) => {
|
|
106
|
+
if (!value) return "Please enter a project name";
|
|
107
|
+
if (!/^[a-z0-9-_]+$/i.test(value))
|
|
108
|
+
return "Only letters, numbers, dashes and underscores";
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
|
|
112
|
+
framework: () =>
|
|
113
|
+
p.select<any, "hono" | "express" | "bun-native">({
|
|
114
|
+
message: "Which framework would you like to use?",
|
|
115
|
+
options: [
|
|
116
|
+
{
|
|
117
|
+
value: "hono",
|
|
118
|
+
label: "Hono",
|
|
119
|
+
hint: "Ultrafast web framework (recommended)",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
value: "express",
|
|
123
|
+
label: "Express",
|
|
124
|
+
hint: "Battle-tested Node.js framework",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
value: "bun-native",
|
|
128
|
+
label: "Bun Native",
|
|
129
|
+
hint: "Native Bun.serve() API",
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
initialValue: "hono",
|
|
133
|
+
}),
|
|
134
|
+
|
|
135
|
+
databases: () =>
|
|
136
|
+
p.multiselect({
|
|
137
|
+
message: "Which database(s) would you like to use?",
|
|
138
|
+
options: [
|
|
139
|
+
{
|
|
140
|
+
value: AvailablePackages.postgres,
|
|
141
|
+
label: "PostgreSQL",
|
|
142
|
+
hint: "Dockerized • Drizzle ORM",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
value: AvailablePackages.mysql,
|
|
146
|
+
label: "MySQL",
|
|
147
|
+
hint: "Dockerized • Drizzle ORM",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
value: AvailablePackages.mongodb,
|
|
151
|
+
label: "MongoDB",
|
|
152
|
+
hint: "Not dockerized • Mongoose",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
value: AvailablePackages.redis,
|
|
156
|
+
label: "Redis",
|
|
157
|
+
hint: "Dockerized • Caching & Sessions",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
required: false,
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
auth: () =>
|
|
164
|
+
p.multiselect({
|
|
165
|
+
message: "Select authentication methods:",
|
|
166
|
+
options: [
|
|
167
|
+
{
|
|
168
|
+
value: AvailablePackages.jwt,
|
|
169
|
+
label: "JWT Token Auth",
|
|
170
|
+
hint: "Stateless tokens",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
value: AvailablePackages.oauth,
|
|
174
|
+
label: "OAuth 2.0",
|
|
175
|
+
hint: "Google, GitHub, etc.",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
value: AvailablePackages.session,
|
|
179
|
+
label: "Session-based Auth",
|
|
180
|
+
hint: "Requires Redis",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
required: false,
|
|
184
|
+
}),
|
|
185
|
+
|
|
186
|
+
// ──────────────────────────────────────────
|
|
187
|
+
// 🤖 AI & ML
|
|
188
|
+
// ──────────────────────────────────────────
|
|
189
|
+
...add_cli_header("🤖 AI & ML"),
|
|
190
|
+
|
|
191
|
+
aiProviders: () =>
|
|
192
|
+
p.multiselect({
|
|
193
|
+
message: "AI Providers: (select all that apply)",
|
|
194
|
+
options: [
|
|
195
|
+
{
|
|
196
|
+
value: AvailablePackages.openai,
|
|
197
|
+
label: "OpenAI",
|
|
198
|
+
hint: "GPT-4, DALL-E, Whisper",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
value: AvailablePackages.anthropic,
|
|
202
|
+
label: "Anthropic Claude",
|
|
203
|
+
hint: "Claude 3.5 Sonnet",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
value: AvailablePackages.gemini,
|
|
207
|
+
label: "Google Gemini",
|
|
208
|
+
hint: "Gemini Pro",
|
|
209
|
+
},
|
|
210
|
+
{ value: "none", label: "None", hint: "Skip AI integration" },
|
|
211
|
+
],
|
|
212
|
+
required: false,
|
|
213
|
+
}),
|
|
214
|
+
|
|
215
|
+
vercelAI: ({ results }) => {
|
|
216
|
+
const hasAI =
|
|
217
|
+
results.aiProviders?.length && results.aiProviders.length > 0 &&
|
|
218
|
+
!results.aiProviders?.includes("none");
|
|
219
|
+
|
|
220
|
+
if (!hasAI) return Promise.resolve(false);
|
|
221
|
+
|
|
222
|
+
return p.confirm({
|
|
223
|
+
message: "Use Vercel AI SDK for unified interface?",
|
|
224
|
+
initialValue: true,
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// ──────────────────────────────────────────
|
|
229
|
+
// 📧 COMMUNICATIONS
|
|
230
|
+
// ──────────────────────────────────────────
|
|
231
|
+
...add_cli_header("📧 COMMUNICATIONS"),
|
|
232
|
+
|
|
233
|
+
emailService: () =>
|
|
234
|
+
p.select<any, AvailablePackages | "none">({
|
|
235
|
+
message: "Email service:",
|
|
236
|
+
options: [
|
|
237
|
+
{
|
|
238
|
+
value: AvailablePackages.resend,
|
|
239
|
+
label: "Resend",
|
|
240
|
+
hint: "Modern, developer-first",
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
value: AvailablePackages.sendgrid,
|
|
244
|
+
label: "SendGrid",
|
|
245
|
+
hint: "Enterprise-grade",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
value: AvailablePackages.nodemailer,
|
|
249
|
+
label: "NodeMailer (SMTP)",
|
|
250
|
+
hint: "Self-hosted",
|
|
251
|
+
},
|
|
252
|
+
{ value: "none", label: "None" },
|
|
253
|
+
],
|
|
254
|
+
initialValue: "none",
|
|
255
|
+
}),
|
|
256
|
+
|
|
257
|
+
realtime: () =>
|
|
258
|
+
p.select<any, AvailablePackages | "none">({
|
|
259
|
+
message: "Real-time capabilities:",
|
|
260
|
+
options: [
|
|
261
|
+
{
|
|
262
|
+
value: AvailablePackages.socketio,
|
|
263
|
+
label: "Socket.io",
|
|
264
|
+
hint: "WebSockets + fallbacks",
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
value: AvailablePackages.sse,
|
|
268
|
+
label: "Server-Sent Events (SSE)",
|
|
269
|
+
hint: "Native, unidirectional",
|
|
270
|
+
},
|
|
271
|
+
{ value: "none", label: "None" },
|
|
272
|
+
],
|
|
273
|
+
initialValue: "none",
|
|
274
|
+
}),
|
|
275
|
+
|
|
276
|
+
// ──────────────────────────────────────────
|
|
277
|
+
// 🔧 INFRASTRUCTURE
|
|
278
|
+
// ──────────────────────────────────────────
|
|
279
|
+
...add_cli_header("🔧 INFRASTRUCTURE"),
|
|
280
|
+
|
|
281
|
+
backgroundJobs: () =>
|
|
282
|
+
p.select<any, AvailablePackages | "none">({
|
|
283
|
+
message: "Background jobs & queues:",
|
|
284
|
+
options: [
|
|
285
|
+
{
|
|
286
|
+
value: AvailablePackages.bullmq,
|
|
287
|
+
label: "BullMQ",
|
|
288
|
+
hint: "Redis-based, feature-rich",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
value: AvailablePackages.inngest,
|
|
292
|
+
label: "Inngest",
|
|
293
|
+
hint: "Serverless-first, durable",
|
|
294
|
+
},
|
|
295
|
+
{ value: "none", label: "None" },
|
|
296
|
+
],
|
|
297
|
+
initialValue: "none",
|
|
298
|
+
}),
|
|
299
|
+
|
|
300
|
+
// ──────────────────────────────────────────
|
|
301
|
+
// 📚 DEVELOPER EXPERIENCE
|
|
302
|
+
// ──────────────────────────────────────────
|
|
303
|
+
...add_cli_header("📚 DEVELOPER EXPERIENCE"),
|
|
304
|
+
|
|
305
|
+
apiDocs: () =>
|
|
306
|
+
p.select<any, AvailablePackages | "none">({
|
|
307
|
+
message: "API Documentation:",
|
|
308
|
+
options: [
|
|
309
|
+
{
|
|
310
|
+
value: AvailablePackages.swagger,
|
|
311
|
+
label: "Swagger/OpenAPI",
|
|
312
|
+
hint: "Industry standard",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
value: AvailablePackages.scalar,
|
|
316
|
+
label: "Scalar",
|
|
317
|
+
hint: "Modern, beautiful UI",
|
|
318
|
+
},
|
|
319
|
+
{ value: "none", label: "None" },
|
|
320
|
+
],
|
|
321
|
+
initialValue: AvailablePackages.swagger,
|
|
322
|
+
}),
|
|
323
|
+
|
|
324
|
+
testing: () =>
|
|
325
|
+
p.confirm({
|
|
326
|
+
message: "Set up testing with Vitest?",
|
|
327
|
+
initialValue: true,
|
|
328
|
+
}),
|
|
329
|
+
|
|
330
|
+
validation: () =>
|
|
331
|
+
p.select<any, AvailablePackages | "none">({
|
|
332
|
+
message: "Request validation library:",
|
|
333
|
+
options: [
|
|
334
|
+
{
|
|
335
|
+
value: AvailablePackages.zod,
|
|
336
|
+
label: "Zod",
|
|
337
|
+
hint: "TypeScript-first, lightweight",
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
value: AvailablePackages.yup,
|
|
341
|
+
label: "Yup",
|
|
342
|
+
hint: "Schema builder, runtime validation",
|
|
343
|
+
},
|
|
344
|
+
{ value: "none", label: "None" },
|
|
345
|
+
],
|
|
346
|
+
initialValue: AvailablePackages.zod,
|
|
347
|
+
}),
|
|
348
|
+
|
|
349
|
+
installDeps: () =>
|
|
350
|
+
p.confirm({
|
|
351
|
+
message: "Install dependencies?",
|
|
352
|
+
initialValue: !cliOptions.noInstall,
|
|
353
|
+
}),
|
|
354
|
+
|
|
355
|
+
initGit: () =>
|
|
356
|
+
p.confirm({
|
|
357
|
+
message: "Initialize git repository?",
|
|
358
|
+
initialValue: !cliOptions.noGit,
|
|
359
|
+
}),
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
onCancel: () => {
|
|
363
|
+
p.cancel("Operation cancelled");
|
|
364
|
+
process.exit(0);
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Build packages array
|
|
370
|
+
const aiPackages = (project.aiProviders as (AvailablePackages | string)[] | undefined)?.filter(p => p !== "none") as AvailablePackages[] || [];
|
|
371
|
+
const packages: AvailablePackages[] = [
|
|
372
|
+
...(project.databases || []),
|
|
373
|
+
...(project.auth || []),
|
|
374
|
+
...aiPackages,
|
|
375
|
+
project.vercelAI ? AvailablePackages.vercelAI : null,
|
|
376
|
+
typeof project.emailService === "string" && project.emailService !== "none" ? project.emailService as AvailablePackages : null,
|
|
377
|
+
typeof project.realtime === "string" && project.realtime !== "none" ? project.realtime as AvailablePackages : null,
|
|
378
|
+
typeof project.backgroundJobs === "string" && project.backgroundJobs !== "none" ? project.backgroundJobs as AvailablePackages : null,
|
|
379
|
+
typeof project.apiDocs === "string" && project.apiDocs !== "none" ? project.apiDocs as AvailablePackages : null,
|
|
380
|
+
project.testing ? AvailablePackages.vitest : null,
|
|
381
|
+
typeof project.validation === "string" && project.validation !== "none" ? project.validation as AvailablePackages : null,
|
|
382
|
+
].filter(Boolean) as AvailablePackages[];
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
appName: project.projectName as string,
|
|
386
|
+
framework: project.framework as "hono" | "express" | "bun-native",
|
|
387
|
+
packages,
|
|
388
|
+
flags: {
|
|
389
|
+
...defaultOptions,
|
|
390
|
+
noInstall: !project.installDeps,
|
|
391
|
+
noGit: !project.initGit,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Helper to add section headers
|
|
397
|
+
function add_cli_header(title: string) {
|
|
398
|
+
return {
|
|
399
|
+
[`_${title.replace(/[^a-zA-Z0-9]/g, "")}`]: () => {
|
|
400
|
+
console.log(chalk.bold.cyan(`\n${title}`));
|
|
401
|
+
return Promise.resolve();
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Build project from CLI flags (CI mode)
|
|
407
|
+
function build_from_flags(
|
|
408
|
+
appName: string | undefined,
|
|
409
|
+
flags: CliFlags & { hono?: boolean; express?: boolean; "bun-native"?: boolean }
|
|
410
|
+
): CliResults {
|
|
411
|
+
const packages: AvailablePackages[] = [];
|
|
412
|
+
|
|
413
|
+
// Determine framework
|
|
414
|
+
let framework: "hono" | "express" | "bun-native" = "hono";
|
|
415
|
+
if (flags.express) framework = "express";
|
|
416
|
+
else if (flags["bun-native"]) framework = "bun-native";
|
|
417
|
+
|
|
418
|
+
// Add packages based on flags
|
|
419
|
+
if (flags.postgres) packages.push(AvailablePackages.postgres);
|
|
420
|
+
if (flags.mysql) packages.push(AvailablePackages.mysql);
|
|
421
|
+
if (flags.mongodb) packages.push(AvailablePackages.mongodb);
|
|
422
|
+
if (flags.redis) packages.push(AvailablePackages.redis);
|
|
423
|
+
if (flags.jwt) packages.push(AvailablePackages.jwt);
|
|
424
|
+
if (flags.oauth) packages.push(AvailablePackages.oauth);
|
|
425
|
+
if (flags.session) packages.push(AvailablePackages.session);
|
|
426
|
+
if (flags.openai) packages.push(AvailablePackages.openai);
|
|
427
|
+
if (flags.anthropic) packages.push(AvailablePackages.anthropic);
|
|
428
|
+
if (flags.gemini) packages.push(AvailablePackages.gemini);
|
|
429
|
+
if (flags.vercelAI) packages.push(AvailablePackages.vercelAI);
|
|
430
|
+
if (flags.resend) packages.push(AvailablePackages.resend);
|
|
431
|
+
if (flags.sendgrid) packages.push(AvailablePackages.sendgrid);
|
|
432
|
+
if (flags.nodemailer) packages.push(AvailablePackages.nodemailer);
|
|
433
|
+
if (flags.socketio) packages.push(AvailablePackages.socketio);
|
|
434
|
+
if (flags.sse) packages.push(AvailablePackages.sse);
|
|
435
|
+
if (flags.bullmq) packages.push(AvailablePackages.bullmq);
|
|
436
|
+
if (flags.inngest) packages.push(AvailablePackages.inngest);
|
|
437
|
+
if (flags.swagger) packages.push(AvailablePackages.swagger);
|
|
438
|
+
if (flags.scalar) packages.push(AvailablePackages.scalar);
|
|
439
|
+
if (flags.vitest) packages.push(AvailablePackages.vitest);
|
|
440
|
+
if (flags.zod) packages.push(AvailablePackages.zod);
|
|
441
|
+
if (flags.yup) packages.push(AvailablePackages.yup);
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
appName: appName || "my-backend",
|
|
445
|
+
framework,
|
|
446
|
+
packages,
|
|
447
|
+
flags,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type CliResults, type InstallerOptions } from "../types/index.js";
|
|
2
|
+
import { parse_name_and_path } from "../utils/parse-name-and-path.js";
|
|
3
|
+
import { scaffold_project } from "./scaffold-project.js";
|
|
4
|
+
import { build_installer_map, run_installers } from "../installers/index.js";
|
|
5
|
+
import { generate_dockerfile } from "./generate-dockerfile.js";
|
|
6
|
+
import { generate_docker_compose } from "./generate-docker-compose.js";
|
|
7
|
+
import { install_dependencies } from "./install-dependencies.js";
|
|
8
|
+
import { init_git } from "./init-git.js";
|
|
9
|
+
import { log_next_steps } from "./log-next-steps.js";
|
|
10
|
+
import { render_title } from "../utils/render-title.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main orchestration function for project creation.
|
|
14
|
+
* Coordinates all steps: parsing, scaffolding, installing packages, and setup.
|
|
15
|
+
*
|
|
16
|
+
* @param cliResults - Results from CLI argument parsing
|
|
17
|
+
*/
|
|
18
|
+
export async function create_project(cliResults: CliResults): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
// Step 1: Parse and validate project name and path
|
|
21
|
+
render_title("Validating project name");
|
|
22
|
+
const { projectName, projectDir } = parse_name_and_path(cliResults.appName);
|
|
23
|
+
|
|
24
|
+
// Step 2: Create installer options
|
|
25
|
+
const installerOptions: InstallerOptions = {
|
|
26
|
+
projectDir,
|
|
27
|
+
appName: projectName,
|
|
28
|
+
framework: cliResults.framework,
|
|
29
|
+
packages: cliResults.packages,
|
|
30
|
+
noInstall: cliResults.flags.noInstall,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Step 3: Scaffold base project structure
|
|
34
|
+
render_title("Creating project structure");
|
|
35
|
+
await scaffold_project(installerOptions);
|
|
36
|
+
|
|
37
|
+
// Step 4: Build installer map and run installers
|
|
38
|
+
render_title("Installing selected packages");
|
|
39
|
+
const installerMap = build_installer_map(installerOptions);
|
|
40
|
+
await run_installers(installerMap, installerOptions);
|
|
41
|
+
|
|
42
|
+
// Step 5: Generate Docker files
|
|
43
|
+
render_title("Generating Docker configuration");
|
|
44
|
+
await generate_dockerfile(installerOptions);
|
|
45
|
+
await generate_docker_compose(installerOptions);
|
|
46
|
+
|
|
47
|
+
// Step 6: Install dependencies (unless --noInstall flag)
|
|
48
|
+
if (!cliResults.flags.noInstall) {
|
|
49
|
+
render_title("Installing dependencies");
|
|
50
|
+
await install_dependencies(projectDir);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Step 7: Initialize Git (unless --noGit flag)
|
|
54
|
+
if (!cliResults.flags.noGit) {
|
|
55
|
+
render_title("Initializing Git repository");
|
|
56
|
+
await init_git(projectDir);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 8: Display next steps
|
|
60
|
+
log_next_steps(installerOptions, cliResults);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("\n❌ Project creation failed:");
|
|
63
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { type InstallerOptions, AvailablePackages } from "../types/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a docker-compose.yml file based on selected packages.
|
|
7
|
+
* Includes services for databases, redis, and the main application.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Installer options with selected packages
|
|
10
|
+
*/
|
|
11
|
+
export async function generate_docker_compose(
|
|
12
|
+
options: InstallerOptions
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const { projectDir, packages, appName } = options;
|
|
15
|
+
|
|
16
|
+
// Check which services are needed
|
|
17
|
+
const hasPostgres = packages.includes(AvailablePackages.postgres);
|
|
18
|
+
const hasMysql = packages.includes(AvailablePackages.mysql);
|
|
19
|
+
const hasMongodb = packages.includes(AvailablePackages.mongodb);
|
|
20
|
+
const hasRedis = packages.includes(AvailablePackages.redis);
|
|
21
|
+
|
|
22
|
+
// Only generate docker-compose if we have any services to include
|
|
23
|
+
if (!hasPostgres && !hasMysql && !hasMongodb && !hasRedis) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let composeContent = `version: '3.8'
|
|
28
|
+
|
|
29
|
+
services:
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
// Add app service
|
|
33
|
+
composeContent += ` app:
|
|
34
|
+
build: .
|
|
35
|
+
ports:
|
|
36
|
+
- "3000:3000"
|
|
37
|
+
environment:
|
|
38
|
+
- NODE_ENV=production
|
|
39
|
+
env_file:
|
|
40
|
+
- .env
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
// Add depends_on for databases
|
|
44
|
+
const dependencies: string[] = [];
|
|
45
|
+
if (hasPostgres) dependencies.push("postgres");
|
|
46
|
+
if (hasMysql) dependencies.push("mysql");
|
|
47
|
+
if (hasMongodb) dependencies.push("mongodb");
|
|
48
|
+
if (hasRedis) dependencies.push("redis");
|
|
49
|
+
|
|
50
|
+
if (dependencies.length > 0) {
|
|
51
|
+
composeContent += ` depends_on:
|
|
52
|
+
`;
|
|
53
|
+
dependencies.forEach((dep) => {
|
|
54
|
+
composeContent += ` - ${dep}\n`;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add PostgreSQL service
|
|
59
|
+
if (hasPostgres) {
|
|
60
|
+
composeContent += `
|
|
61
|
+
postgres:
|
|
62
|
+
image: postgres:16-alpine
|
|
63
|
+
environment:
|
|
64
|
+
POSTGRES_USER: \${POSTGRES_USER:-user}
|
|
65
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-password}
|
|
66
|
+
POSTGRES_DB: \${POSTGRES_DB:-${appName}}
|
|
67
|
+
ports:
|
|
68
|
+
- "5432:5432"
|
|
69
|
+
volumes:
|
|
70
|
+
- postgres_data:/var/lib/postgresql/data
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add MySQL service
|
|
75
|
+
if (hasMysql) {
|
|
76
|
+
composeContent += `
|
|
77
|
+
mysql:
|
|
78
|
+
image: mysql:8.0
|
|
79
|
+
environment:
|
|
80
|
+
MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-rootpassword}
|
|
81
|
+
MYSQL_DATABASE: \${MYSQL_DATABASE:-${appName}}
|
|
82
|
+
MYSQL_USER: \${MYSQL_USER:-user}
|
|
83
|
+
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-password}
|
|
84
|
+
ports:
|
|
85
|
+
- "3306:3306"
|
|
86
|
+
volumes:
|
|
87
|
+
- mysql_data:/var/lib/mysql
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Add MongoDB service
|
|
92
|
+
if (hasMongodb) {
|
|
93
|
+
composeContent += `
|
|
94
|
+
mongodb:
|
|
95
|
+
image: mongo:7.0
|
|
96
|
+
environment:
|
|
97
|
+
MONGO_INITDB_ROOT_USERNAME: \${MONGO_ROOT_USER:-root}
|
|
98
|
+
MONGO_INITDB_ROOT_PASSWORD: \${MONGO_ROOT_PASSWORD:-password}
|
|
99
|
+
MONGO_INITDB_DATABASE: \${MONGO_DATABASE:-${appName}}
|
|
100
|
+
ports:
|
|
101
|
+
- "27017:27017"
|
|
102
|
+
volumes:
|
|
103
|
+
- mongodb_data:/data/db
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add Redis service
|
|
108
|
+
if (hasRedis) {
|
|
109
|
+
composeContent += `
|
|
110
|
+
redis:
|
|
111
|
+
image: redis:7-alpine
|
|
112
|
+
ports:
|
|
113
|
+
- "6379:6379"
|
|
114
|
+
volumes:
|
|
115
|
+
- redis_data:/data
|
|
116
|
+
command: redis-server --appendonly yes
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Add volumes section
|
|
121
|
+
composeContent += `
|
|
122
|
+
volumes:
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
if (hasPostgres) composeContent += ` postgres_data:\n`;
|
|
126
|
+
if (hasMysql) composeContent += ` mysql_data:\n`;
|
|
127
|
+
if (hasMongodb) composeContent += ` mongodb_data:\n`;
|
|
128
|
+
if (hasRedis) composeContent += ` redis_data:\n`;
|
|
129
|
+
|
|
130
|
+
// Write docker-compose.yml
|
|
131
|
+
const dockerComposePath = path.join(projectDir, "docker-compose.yml");
|
|
132
|
+
await fs.writeFile(dockerComposePath, composeContent);
|
|
133
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { type InstallerOptions } from "../types/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a Dockerfile for the project.
|
|
7
|
+
* Uses Bun's official image with multi-stage build for production optimization.
|
|
8
|
+
*
|
|
9
|
+
* @param options - Installer options
|
|
10
|
+
*/
|
|
11
|
+
export async function generate_dockerfile(
|
|
12
|
+
options: InstallerOptions
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const { projectDir } = options;
|
|
15
|
+
|
|
16
|
+
const dockerfileContent = `# Use Bun's official image
|
|
17
|
+
FROM oven/bun:1 as base
|
|
18
|
+
WORKDIR /app
|
|
19
|
+
|
|
20
|
+
# Install dependencies
|
|
21
|
+
FROM base AS dependencies
|
|
22
|
+
COPY package.json bun.lockb ./
|
|
23
|
+
RUN bun install --frozen-lockfile
|
|
24
|
+
|
|
25
|
+
# Copy source code
|
|
26
|
+
FROM base AS build
|
|
27
|
+
COPY --from=dependencies /app/node_modules ./node_modules
|
|
28
|
+
COPY . .
|
|
29
|
+
|
|
30
|
+
# Production image
|
|
31
|
+
FROM base AS production
|
|
32
|
+
ENV NODE_ENV=production
|
|
33
|
+
COPY --from=dependencies /app/node_modules ./node_modules
|
|
34
|
+
COPY --from=build /app .
|
|
35
|
+
|
|
36
|
+
# Expose port
|
|
37
|
+
EXPOSE 3000
|
|
38
|
+
|
|
39
|
+
# Run the application
|
|
40
|
+
CMD ["bun", "run", "src/index.ts"]
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const dockerfilePath = path.join(projectDir, "Dockerfile");
|
|
44
|
+
await fs.writeFile(dockerfilePath, dockerfileContent);
|
|
45
|
+
}
|