@buenojs/bueno 0.8.3 → 0.8.4
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/cli/index.js +2949 -1415
- package/package.json +1 -1
- package/src/cli/commands/new.ts +155 -263
- package/src/cli/templates/database/index.ts +42 -0
- package/src/cli/templates/database/mysql.ts +14 -0
- package/src/cli/templates/database/none.ts +16 -0
- package/src/cli/templates/database/postgresql.ts +14 -0
- package/src/cli/templates/database/sqlite.ts +14 -0
- package/src/cli/templates/frontend/index.ts +45 -0
- package/src/cli/templates/frontend/none.ts +17 -0
- package/src/cli/templates/frontend/react.ts +140 -0
- package/src/cli/templates/frontend/solid.ts +134 -0
- package/src/cli/templates/frontend/svelte.ts +131 -0
- package/src/cli/templates/frontend/vue.ts +130 -0
- package/src/cli/templates/generators/index.ts +339 -0
- package/src/cli/templates/generators/types.ts +56 -0
- package/src/cli/templates/index.ts +34 -1
- package/src/cli/templates/project/api.ts +80 -0
- package/src/cli/templates/project/default.ts +140 -0
- package/src/cli/templates/project/fullstack.ts +109 -0
- package/src/cli/templates/project/index.ts +56 -0
- package/src/cli/templates/project/minimal.ts +40 -0
- package/src/cli/templates/project/types.ts +89 -0
- package/src/cli/templates/project/website.ts +263 -0
- package/src/cli/utils/index.ts +2 -1
- package/src/cli/utils/version.ts +41 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,1300 +1,2927 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __export = (target, all) => {
|
|
5
|
+
for (var name in all)
|
|
6
|
+
__defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
set: (newValue) => all[name] = () => newValue
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
14
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
15
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
16
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
17
|
+
else
|
|
18
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
19
|
+
if (d = decorators[i])
|
|
20
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
21
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
22
|
+
};
|
|
23
|
+
var __legacyMetadataTS = (k, v) => {
|
|
24
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
25
|
+
return Reflect.metadata(k, v);
|
|
26
|
+
};
|
|
27
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
3
28
|
var __require = import.meta.require;
|
|
4
29
|
|
|
5
|
-
// src/cli/
|
|
30
|
+
// src/cli/utils/version.ts
|
|
6
31
|
import { readFileSync as readFileSync2 } from "fs";
|
|
7
32
|
import { join as join2 } from "path";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const result = {
|
|
12
|
-
command: "",
|
|
13
|
-
positionals: [],
|
|
14
|
-
options: {},
|
|
15
|
-
flags: new Set
|
|
16
|
-
};
|
|
17
|
-
for (let i = 0;i < argv.length; i++) {
|
|
18
|
-
const arg = argv[i];
|
|
19
|
-
if (!arg)
|
|
20
|
-
continue;
|
|
21
|
-
if (arg.startsWith("--")) {
|
|
22
|
-
const eqIndex = arg.indexOf("=");
|
|
23
|
-
if (eqIndex !== -1) {
|
|
24
|
-
const name = arg.slice(2, eqIndex);
|
|
25
|
-
const value = arg.slice(eqIndex + 1);
|
|
26
|
-
result.options[name] = value;
|
|
27
|
-
} else {
|
|
28
|
-
const name = arg.slice(2);
|
|
29
|
-
const nextArg = argv[i + 1];
|
|
30
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
31
|
-
result.options[name] = true;
|
|
32
|
-
result.flags.add(name);
|
|
33
|
-
} else {
|
|
34
|
-
result.options[name] = nextArg;
|
|
35
|
-
i++;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
} else if (arg.startsWith("-") && arg.length > 1) {
|
|
39
|
-
const chars = arg.slice(1);
|
|
40
|
-
if (chars.length > 1) {
|
|
41
|
-
for (const char of chars) {
|
|
42
|
-
result.options[char] = true;
|
|
43
|
-
result.flags.add(char);
|
|
44
|
-
}
|
|
45
|
-
} else {
|
|
46
|
-
const name = chars;
|
|
47
|
-
const nextArg = argv[i + 1];
|
|
48
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
49
|
-
result.options[name] = true;
|
|
50
|
-
result.flags.add(name);
|
|
51
|
-
} else {
|
|
52
|
-
result.options[name] = nextArg;
|
|
53
|
-
i++;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
if (!result.command) {
|
|
58
|
-
result.command = arg;
|
|
59
|
-
} else {
|
|
60
|
-
result.positionals.push(arg);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
function getOption(parsed, name, definition) {
|
|
67
|
-
const value = parsed.options[name] ?? parsed.options[definition.alias ?? ""];
|
|
68
|
-
if (value === undefined) {
|
|
69
|
-
return definition.default;
|
|
70
|
-
}
|
|
71
|
-
if (definition.type === "boolean") {
|
|
72
|
-
return value === true || value === "true";
|
|
73
|
-
}
|
|
74
|
-
if (definition.type === "number") {
|
|
75
|
-
return typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
|
|
33
|
+
function getBuenoVersion() {
|
|
34
|
+
if (cachedVersion) {
|
|
35
|
+
return cachedVersion;
|
|
76
36
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (let i = 0;i < argv.length; i++) {
|
|
86
|
-
const arg = argv[i];
|
|
87
|
-
if (!arg)
|
|
88
|
-
continue;
|
|
89
|
-
if (arg === `--${name}`) {
|
|
90
|
-
const nextArg = argv[i + 1];
|
|
91
|
-
if (nextArg && !nextArg.startsWith("-")) {
|
|
92
|
-
values.push(nextArg);
|
|
93
|
-
i++;
|
|
94
|
-
}
|
|
95
|
-
} else if (arg.startsWith(`--${name}=`)) {
|
|
96
|
-
const value = arg.slice(name.length + 3);
|
|
97
|
-
values.push(value);
|
|
98
|
-
} else if (alias && arg === `-${alias}`) {
|
|
99
|
-
const nextArg = argv[i + 1];
|
|
100
|
-
if (nextArg && !nextArg.startsWith("-")) {
|
|
101
|
-
values.push(nextArg);
|
|
102
|
-
i++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
37
|
+
try {
|
|
38
|
+
const packageJsonPath = join2(import.meta.dir, "..", "..", "..", "package.json");
|
|
39
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
40
|
+
cachedVersion = `^${packageJson.version}`;
|
|
41
|
+
return cachedVersion;
|
|
42
|
+
} catch {
|
|
43
|
+
console.warn("Could not read version from package.json, using default");
|
|
44
|
+
return "^0.8.0";
|
|
105
45
|
}
|
|
106
|
-
return values;
|
|
107
46
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`);
|
|
113
|
-
lines.push("Usage:");
|
|
114
|
-
let usage = ` ${cliName} ${command.name}`;
|
|
115
|
-
if (command.positionals) {
|
|
116
|
-
for (const pos of command.positionals) {
|
|
117
|
-
usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
usage += " [options]";
|
|
121
|
-
lines.push(usage + `
|
|
122
|
-
`);
|
|
123
|
-
if (command.positionals && command.positionals.length > 0) {
|
|
124
|
-
lines.push("Arguments:");
|
|
125
|
-
for (const pos of command.positionals) {
|
|
126
|
-
const required = pos.required ? " (required)" : "";
|
|
127
|
-
lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
|
|
128
|
-
}
|
|
129
|
-
lines.push("");
|
|
130
|
-
}
|
|
131
|
-
if (command.options && command.options.length > 0) {
|
|
132
|
-
lines.push("Options:");
|
|
133
|
-
for (const opt of command.options) {
|
|
134
|
-
let flag = `--${opt.name}`;
|
|
135
|
-
if (opt.alias) {
|
|
136
|
-
flag = `-${opt.alias}, ${flag}`;
|
|
137
|
-
}
|
|
138
|
-
let defaultValue = "";
|
|
139
|
-
if (opt.default !== undefined) {
|
|
140
|
-
defaultValue = ` (default: ${opt.default})`;
|
|
141
|
-
}
|
|
142
|
-
lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
|
|
143
|
-
}
|
|
144
|
-
lines.push("");
|
|
145
|
-
}
|
|
146
|
-
if (command.examples && command.examples.length > 0) {
|
|
147
|
-
lines.push("Examples:");
|
|
148
|
-
for (const example of command.examples) {
|
|
149
|
-
lines.push(` ${example}`);
|
|
150
|
-
}
|
|
151
|
-
lines.push("");
|
|
152
|
-
}
|
|
153
|
-
return lines.join(`
|
|
154
|
-
`);
|
|
47
|
+
function getBuenoDependency() {
|
|
48
|
+
return {
|
|
49
|
+
"@buenojs/bueno": getBuenoVersion()
|
|
50
|
+
};
|
|
155
51
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
52
|
+
var cachedVersion = null;
|
|
53
|
+
var init_version = () => {};
|
|
54
|
+
|
|
55
|
+
// src/cli/templates/docker.ts
|
|
56
|
+
function getDockerfileTemplate(projectName, database) {
|
|
57
|
+
return `# ${projectName} - Production Dockerfile
|
|
58
|
+
# Multi-stage build for optimized production image
|
|
59
|
+
|
|
60
|
+
# Stage 1: Install dependencies
|
|
61
|
+
FROM oven/bun:1 AS deps
|
|
62
|
+
|
|
63
|
+
WORKDIR /app
|
|
64
|
+
|
|
65
|
+
# Copy package files first for better layer caching
|
|
66
|
+
COPY package.json bun.lock* ./
|
|
67
|
+
|
|
68
|
+
# Install dependencies
|
|
69
|
+
RUN bun install --frozen-lockfile --production
|
|
70
|
+
|
|
71
|
+
# Stage 2: Build the application
|
|
72
|
+
FROM oven/bun:1 AS builder
|
|
73
|
+
|
|
74
|
+
WORKDIR /app
|
|
75
|
+
|
|
76
|
+
# Copy package files
|
|
77
|
+
COPY package.json bun.lock* ./
|
|
78
|
+
|
|
79
|
+
# Install all dependencies (including devDependencies for build)
|
|
80
|
+
RUN bun install --frozen-lockfile
|
|
81
|
+
|
|
82
|
+
# Copy source code
|
|
83
|
+
COPY . .
|
|
84
|
+
|
|
85
|
+
# Build the application
|
|
86
|
+
RUN bun run build
|
|
87
|
+
|
|
88
|
+
# Stage 3: Production image
|
|
89
|
+
FROM oven/bun:1 AS runner
|
|
90
|
+
|
|
91
|
+
WORKDIR /app
|
|
92
|
+
|
|
93
|
+
# Set production environment
|
|
94
|
+
ENV NODE_ENV=production
|
|
95
|
+
ENV BUN_ENV=production
|
|
96
|
+
|
|
97
|
+
# Create non-root user for security
|
|
98
|
+
RUN addgroup --system --gid 1001 bunjs \\
|
|
99
|
+
&& adduser --system --uid 1001 --ingroup bunjs bunuser
|
|
100
|
+
|
|
101
|
+
# Copy built application from builder
|
|
102
|
+
COPY --from=builder /app/dist ./dist
|
|
103
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
104
|
+
COPY --from=builder /app/package.json ./
|
|
105
|
+
|
|
106
|
+
# Copy config files if they exist
|
|
107
|
+
COPY --from=builder /app/bueno.config.ts* ./
|
|
108
|
+
|
|
109
|
+
# Set proper ownership
|
|
110
|
+
RUN chown -R bunuser:bunjs /app
|
|
111
|
+
|
|
112
|
+
# Switch to non-root user
|
|
113
|
+
USER bunuser
|
|
114
|
+
|
|
115
|
+
# Expose the application port
|
|
116
|
+
EXPOSE 3000
|
|
117
|
+
|
|
118
|
+
# Health check
|
|
119
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
120
|
+
CMD curl -f http://localhost:3000/health || exit 1
|
|
121
|
+
|
|
122
|
+
# Start the application
|
|
123
|
+
CMD ["bun", "run", "dist/main.js"]
|
|
124
|
+
`;
|
|
181
125
|
}
|
|
126
|
+
function getDockerignoreTemplate() {
|
|
127
|
+
return `# Dependencies
|
|
128
|
+
node_modules/
|
|
182
129
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
130
|
+
# Build output
|
|
131
|
+
dist/
|
|
132
|
+
|
|
133
|
+
# Environment files
|
|
134
|
+
.env
|
|
135
|
+
.env.local
|
|
136
|
+
.env.*.local
|
|
137
|
+
|
|
138
|
+
# IDE
|
|
139
|
+
.idea/
|
|
140
|
+
.vscode/
|
|
141
|
+
*.swp
|
|
142
|
+
*.swo
|
|
143
|
+
|
|
144
|
+
# OS
|
|
145
|
+
.DS_Store
|
|
146
|
+
Thumbs.db
|
|
147
|
+
|
|
148
|
+
# Git
|
|
149
|
+
.git/
|
|
150
|
+
.gitignore
|
|
151
|
+
|
|
152
|
+
# Docker
|
|
153
|
+
Dockerfile
|
|
154
|
+
docker-compose*.yml
|
|
155
|
+
.dockerignore
|
|
156
|
+
|
|
157
|
+
# Test files
|
|
158
|
+
tests/
|
|
159
|
+
coverage/
|
|
160
|
+
*.test.ts
|
|
161
|
+
*.spec.ts
|
|
162
|
+
|
|
163
|
+
# Documentation
|
|
164
|
+
*.md
|
|
165
|
+
!README.md
|
|
166
|
+
|
|
167
|
+
# Database files (local)
|
|
168
|
+
*.db
|
|
169
|
+
*.sqlite
|
|
170
|
+
*.sqlite3
|
|
171
|
+
|
|
172
|
+
# Logs
|
|
173
|
+
*.log
|
|
174
|
+
logs/
|
|
175
|
+
|
|
176
|
+
# Misc
|
|
177
|
+
.editorconfig
|
|
178
|
+
.eslintrc*
|
|
179
|
+
.prettierrc*
|
|
180
|
+
tsconfig.json
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
function getDockerComposeTemplate(projectName, database) {
|
|
184
|
+
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
185
|
+
let databaseServices = "";
|
|
186
|
+
let dependsOn = "";
|
|
187
|
+
if (database === "postgresql") {
|
|
188
|
+
databaseServices = `
|
|
189
|
+
# PostgreSQL Database
|
|
190
|
+
postgres:
|
|
191
|
+
image: postgres:16-alpine
|
|
192
|
+
container_name: ${kebabName}-postgres
|
|
193
|
+
restart: unless-stopped
|
|
194
|
+
environment:
|
|
195
|
+
POSTGRES_USER: \${POSTGRES_USER:-postgres}
|
|
196
|
+
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
197
|
+
POSTGRES_DB: \${POSTGRES_DB:-${kebabName}}
|
|
198
|
+
volumes:
|
|
199
|
+
- postgres_data:/var/lib/postgresql/data
|
|
200
|
+
ports:
|
|
201
|
+
- "\${POSTGRES_PORT:-5432}:5432"
|
|
202
|
+
healthcheck:
|
|
203
|
+
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-${kebabName}}"]
|
|
204
|
+
interval: 10s
|
|
205
|
+
timeout: 5s
|
|
206
|
+
retries: 5
|
|
207
|
+
networks:
|
|
208
|
+
- bueno-network
|
|
209
|
+
|
|
210
|
+
`;
|
|
211
|
+
dependsOn = `
|
|
212
|
+
depends_on:
|
|
213
|
+
postgres:
|
|
214
|
+
condition: service_healthy
|
|
215
|
+
`;
|
|
216
|
+
} else if (database === "mysql") {
|
|
217
|
+
databaseServices = `
|
|
218
|
+
# MySQL Database
|
|
219
|
+
mysql:
|
|
220
|
+
image: mysql:8.0
|
|
221
|
+
container_name: ${kebabName}-mysql
|
|
222
|
+
restart: unless-stopped
|
|
223
|
+
environment:
|
|
224
|
+
MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-root}
|
|
225
|
+
MYSQL_USER: \${MYSQL_USER:-mysql}
|
|
226
|
+
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-mysql}
|
|
227
|
+
MYSQL_DATABASE: \${MYSQL_DATABASE:-${kebabName}}
|
|
228
|
+
volumes:
|
|
229
|
+
- mysql_data:/var/lib/mysql
|
|
230
|
+
ports:
|
|
231
|
+
- "\${MYSQL_PORT:-3306}:3306"
|
|
232
|
+
healthcheck:
|
|
233
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p\${MYSQL_ROOT_PASSWORD:-root}"]
|
|
234
|
+
interval: 10s
|
|
235
|
+
timeout: 5s
|
|
236
|
+
retries: 5
|
|
237
|
+
networks:
|
|
238
|
+
- bueno-network
|
|
239
|
+
|
|
240
|
+
`;
|
|
241
|
+
dependsOn = `
|
|
242
|
+
depends_on:
|
|
243
|
+
mysql:
|
|
244
|
+
condition: service_healthy
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
const volumes = database === "postgresql" ? `
|
|
248
|
+
volumes:
|
|
249
|
+
postgres_data:
|
|
250
|
+
driver: local
|
|
251
|
+
` : database === "mysql" ? `
|
|
252
|
+
volumes:
|
|
253
|
+
mysql_data:
|
|
254
|
+
driver: local
|
|
255
|
+
` : "";
|
|
256
|
+
const databaseEnv = database === "postgresql" ? ` DATABASE_URL: postgresql://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres}@postgres:5432/\${POSTGRES_DB:-${kebabName}}
|
|
257
|
+
` : database === "mysql" ? ` DATABASE_URL: mysql://\${MYSQL_USER:-mysql}:\${MYSQL_PASSWORD:-mysql}@mysql:3306/\${MYSQL_DATABASE:-${kebabName}}
|
|
258
|
+
` : "";
|
|
259
|
+
return `# ${projectName} - Docker Compose for Local Development
|
|
260
|
+
# Usage: docker-compose up -d
|
|
261
|
+
|
|
262
|
+
services:
|
|
263
|
+
# Application Service
|
|
264
|
+
app:
|
|
265
|
+
build:
|
|
266
|
+
context: .
|
|
267
|
+
dockerfile: Dockerfile
|
|
268
|
+
container_name: ${kebabName}-app
|
|
269
|
+
restart: unless-stopped
|
|
270
|
+
ports:
|
|
271
|
+
- "\${APP_PORT:-3000}:3000"
|
|
272
|
+
environment:
|
|
273
|
+
NODE_ENV: production
|
|
274
|
+
BUN_ENV: production
|
|
275
|
+
${databaseEnv}${dependsOn} networks:
|
|
276
|
+
- bueno-network
|
|
277
|
+
healthcheck:
|
|
278
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
279
|
+
interval: 30s
|
|
280
|
+
timeout: 10s
|
|
281
|
+
retries: 3
|
|
282
|
+
start_period: 10s
|
|
283
|
+
${databaseServices}networks:
|
|
284
|
+
bueno-network:
|
|
285
|
+
driver: bridge
|
|
286
|
+
${volumes}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
function getDockerEnvTemplate(projectName, database) {
|
|
290
|
+
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
291
|
+
let dbEnv = "";
|
|
292
|
+
if (database === "postgresql") {
|
|
293
|
+
dbEnv = `
|
|
294
|
+
# PostgreSQL Configuration
|
|
295
|
+
POSTGRES_USER=postgres
|
|
296
|
+
POSTGRES_PASSWORD=postgres
|
|
297
|
+
POSTGRES_DB=${kebabName}
|
|
298
|
+
POSTGRES_PORT=5432
|
|
299
|
+
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/${kebabName}
|
|
300
|
+
`;
|
|
301
|
+
} else if (database === "mysql") {
|
|
302
|
+
dbEnv = `
|
|
303
|
+
# MySQL Configuration
|
|
304
|
+
MYSQL_ROOT_PASSWORD=root
|
|
305
|
+
MYSQL_USER=mysql
|
|
306
|
+
MYSQL_PASSWORD=mysql
|
|
307
|
+
MYSQL_DATABASE=${kebabName}
|
|
308
|
+
MYSQL_PORT=3306
|
|
309
|
+
DATABASE_URL=mysql://mysql:mysql@localhost:3306/${kebabName}
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
return `# ${projectName} - Docker Environment Variables
|
|
313
|
+
# Copy this file to .env and update values as needed
|
|
314
|
+
|
|
315
|
+
# Application
|
|
316
|
+
APP_PORT=3000
|
|
317
|
+
NODE_ENV=production
|
|
318
|
+
${dbEnv}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/cli/templates/deploy.ts
|
|
322
|
+
function getRenderYamlTemplate(projectName, database) {
|
|
323
|
+
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
324
|
+
let databaseSection = "";
|
|
325
|
+
let envVars = "";
|
|
326
|
+
if (database === "postgresql") {
|
|
327
|
+
databaseSection = `
|
|
328
|
+
# PostgreSQL Database
|
|
329
|
+
- type: pserv
|
|
330
|
+
name: ${kebabName}-db
|
|
331
|
+
env: docker
|
|
332
|
+
region: oregon
|
|
333
|
+
plan: starter
|
|
334
|
+
envVars:
|
|
335
|
+
- key: POSTGRES_USER
|
|
336
|
+
generateValue: true
|
|
337
|
+
- key: POSTGRES_PASSWORD
|
|
338
|
+
generateValue: true
|
|
339
|
+
- key: POSTGRES_DB
|
|
340
|
+
value: ${kebabName}
|
|
341
|
+
disk:
|
|
342
|
+
name: postgres-data
|
|
343
|
+
mountPath: /var/lib/postgresql/data
|
|
344
|
+
sizeGB: 10
|
|
345
|
+
|
|
346
|
+
`;
|
|
347
|
+
envVars = `
|
|
348
|
+
envVars:
|
|
349
|
+
- key: DATABASE_URL
|
|
350
|
+
fromDatabase:
|
|
351
|
+
name: ${kebabName}-db
|
|
352
|
+
property: connectionString
|
|
353
|
+
- key: NODE_ENV
|
|
354
|
+
value: production
|
|
355
|
+
- key: BUN_ENV
|
|
356
|
+
value: production
|
|
357
|
+
`;
|
|
358
|
+
} else if (database === "mysql") {
|
|
359
|
+
databaseSection = `
|
|
360
|
+
# MySQL Database (using Render's managed MySQL)
|
|
361
|
+
- type: pserv
|
|
362
|
+
name: ${kebabName}-db
|
|
363
|
+
env: docker
|
|
364
|
+
region: oregon
|
|
365
|
+
plan: starter
|
|
366
|
+
envVars:
|
|
367
|
+
- key: MYSQL_ROOT_PASSWORD
|
|
368
|
+
generateValue: true
|
|
369
|
+
- key: MYSQL_USER
|
|
370
|
+
generateValue: true
|
|
371
|
+
- key: MYSQL_PASSWORD
|
|
372
|
+
generateValue: true
|
|
373
|
+
- key: MYSQL_DATABASE
|
|
374
|
+
value: ${kebabName}
|
|
375
|
+
disk:
|
|
376
|
+
name: mysql-data
|
|
377
|
+
mountPath: /var/lib/mysql
|
|
378
|
+
sizeGB: 10
|
|
379
|
+
|
|
380
|
+
`;
|
|
381
|
+
envVars = `
|
|
382
|
+
envVars:
|
|
383
|
+
- key: DATABASE_URL
|
|
384
|
+
fromDatabase:
|
|
385
|
+
name: ${kebabName}-db
|
|
386
|
+
property: connectionString
|
|
387
|
+
- key: NODE_ENV
|
|
388
|
+
value: production
|
|
389
|
+
- key: BUN_ENV
|
|
390
|
+
value: production
|
|
391
|
+
`;
|
|
392
|
+
} else {
|
|
393
|
+
envVars = `
|
|
394
|
+
envVars:
|
|
395
|
+
- key: NODE_ENV
|
|
396
|
+
value: production
|
|
397
|
+
- key: BUN_ENV
|
|
398
|
+
value: production
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
return `# ${projectName} - Render.com Deployment Configuration
|
|
402
|
+
# https://render.com/docs/blueprint-spec
|
|
403
|
+
|
|
404
|
+
services:
|
|
405
|
+
# Web Service
|
|
406
|
+
- type: web
|
|
407
|
+
name: ${kebabName}
|
|
408
|
+
env: docker
|
|
409
|
+
region: oregon
|
|
410
|
+
plan: starter
|
|
411
|
+
branch: main
|
|
412
|
+
dockerfilePath: ./Dockerfile
|
|
413
|
+
# dockerContext: .
|
|
414
|
+
numInstances: 1
|
|
415
|
+
healthCheckPath: /health
|
|
416
|
+
${envVars} # Auto-deploy on push to main branch
|
|
417
|
+
autoDeploy: true
|
|
418
|
+
${databaseSection}
|
|
419
|
+
# Blueprint metadata
|
|
420
|
+
metadata:
|
|
421
|
+
name: ${projectName}
|
|
422
|
+
description: A Bueno application deployed on Render
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
function getFlyTomlTemplate(projectName) {
|
|
426
|
+
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
427
|
+
return `# ${projectName} - Fly.io Deployment Configuration
|
|
428
|
+
# https://fly.io/docs/reference/configuration/
|
|
429
|
+
|
|
430
|
+
app = "${kebabName}"
|
|
431
|
+
primary_region = "sea"
|
|
432
|
+
|
|
433
|
+
[build]
|
|
434
|
+
dockerfile = "Dockerfile"
|
|
435
|
+
|
|
436
|
+
[env]
|
|
437
|
+
NODE_ENV = "production"
|
|
438
|
+
BUN_ENV = "production"
|
|
439
|
+
PORT = "3000"
|
|
440
|
+
|
|
441
|
+
[http_service]
|
|
442
|
+
internal_port = 3000
|
|
443
|
+
force_https = true
|
|
444
|
+
auto_stop_machines = "stop"
|
|
445
|
+
auto_start_machines = true
|
|
446
|
+
min_machines_running = 0
|
|
447
|
+
processes = ["app"]
|
|
448
|
+
|
|
449
|
+
[http_service.concurrency]
|
|
450
|
+
type = "connections"
|
|
451
|
+
hard_limit = 100
|
|
452
|
+
soft_limit = 80
|
|
453
|
+
|
|
454
|
+
[[http_service.checks]]
|
|
455
|
+
grace_period = "10s"
|
|
456
|
+
interval = "30s"
|
|
457
|
+
method = "GET"
|
|
458
|
+
timeout = "5s"
|
|
459
|
+
path = "/health"
|
|
460
|
+
|
|
461
|
+
[http_service.checks.headers]
|
|
462
|
+
Content-Type = "application/json"
|
|
463
|
+
|
|
464
|
+
[[vm]]
|
|
465
|
+
cpu_kind = "shared"
|
|
466
|
+
cpus = 1
|
|
467
|
+
memory_mb = 512
|
|
468
|
+
|
|
469
|
+
[[mounts]]
|
|
470
|
+
source = "data"
|
|
471
|
+
destination = "/app/data"
|
|
472
|
+
initial_size = "1GB"
|
|
473
|
+
|
|
474
|
+
# Scale configuration
|
|
475
|
+
# Use: fly scale count 2 # Scale to 2 machines
|
|
476
|
+
# Use: fly scale vm shared-cpu-2x --memory 1024 # Upgrade VM
|
|
477
|
+
|
|
478
|
+
# Secrets (set via: fly secrets set KEY=VALUE)
|
|
479
|
+
# DATABASE_URL=your-database-url
|
|
480
|
+
# Any other sensitive environment variables
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
function getRailwayTomlTemplate(projectName) {
|
|
484
|
+
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
485
|
+
return `# ${projectName} - Railway Deployment Configuration
|
|
486
|
+
# https://docs.railway.app/reference/config-as-code
|
|
487
|
+
|
|
488
|
+
[build]
|
|
489
|
+
builder = "DOCKERFILE"
|
|
490
|
+
dockerfilePath = "Dockerfile"
|
|
491
|
+
|
|
492
|
+
[deploy]
|
|
493
|
+
startCommand = "bun run dist/main.js"
|
|
494
|
+
healthcheckPath = "/health"
|
|
495
|
+
healthcheckTimeout = 300
|
|
496
|
+
restartPolicyType = "ON_FAILURE"
|
|
497
|
+
restartPolicyMaxRetries = 3
|
|
498
|
+
|
|
499
|
+
# Environment variables
|
|
500
|
+
# Set these in Railway dashboard or via CLI:
|
|
501
|
+
# railway variables set NODE_ENV=production
|
|
502
|
+
# railway variables set DATABASE_URL=your-database-url
|
|
503
|
+
|
|
504
|
+
[[services]]
|
|
505
|
+
name = "${kebabName}"
|
|
506
|
+
|
|
507
|
+
[services.variables]
|
|
508
|
+
NODE_ENV = "production"
|
|
509
|
+
BUN_ENV = "production"
|
|
510
|
+
PORT = "3000"
|
|
511
|
+
|
|
512
|
+
# Health check configuration
|
|
513
|
+
[[services.healthchecks]]
|
|
514
|
+
path = "/health"
|
|
515
|
+
interval = 30
|
|
516
|
+
timeout = 10
|
|
517
|
+
threshold = 3
|
|
518
|
+
|
|
519
|
+
# Resource configuration
|
|
520
|
+
# Adjust in Railway dashboard or via CLI:
|
|
521
|
+
# railway up --memory 512 --cpu 0.5
|
|
522
|
+
|
|
523
|
+
# Scaling configuration
|
|
524
|
+
# Use Railway's autoscaling in dashboard:
|
|
525
|
+
# Min instances: 0 (scale to zero)
|
|
526
|
+
# Max instances: 3
|
|
527
|
+
# Target CPU: 70%
|
|
528
|
+
# Target Memory: 80%
|
|
529
|
+
`;
|
|
530
|
+
}
|
|
531
|
+
function getDeployTemplate(platform, projectName, database) {
|
|
532
|
+
switch (platform) {
|
|
533
|
+
case "render":
|
|
534
|
+
return getRenderYamlTemplate(projectName, database);
|
|
535
|
+
case "fly":
|
|
536
|
+
return getFlyTomlTemplate(projectName);
|
|
537
|
+
case "railway":
|
|
538
|
+
return getRailwayTomlTemplate(projectName);
|
|
539
|
+
default:
|
|
540
|
+
throw new Error(`Unknown deployment platform: ${platform}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function getDeployFilename(platform) {
|
|
544
|
+
switch (platform) {
|
|
545
|
+
case "render":
|
|
546
|
+
return "render.yaml";
|
|
547
|
+
case "fly":
|
|
548
|
+
return "fly.toml";
|
|
549
|
+
case "railway":
|
|
550
|
+
return "railway.toml";
|
|
551
|
+
default:
|
|
552
|
+
throw new Error(`Unknown deployment platform: ${platform}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function getDeployPlatformName(platform) {
|
|
556
|
+
switch (platform) {
|
|
557
|
+
case "render":
|
|
558
|
+
return "Render.com";
|
|
559
|
+
case "fly":
|
|
560
|
+
return "Fly.io";
|
|
561
|
+
case "railway":
|
|
562
|
+
return "Railway";
|
|
563
|
+
default:
|
|
564
|
+
return platform;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/cli/templates/frontend/react.ts
|
|
569
|
+
function reactTemplate() {
|
|
570
|
+
return {
|
|
571
|
+
files: [
|
|
572
|
+
{
|
|
573
|
+
path: "client/index.html",
|
|
574
|
+
content: `<!DOCTYPE html>
|
|
575
|
+
<html lang="en">
|
|
576
|
+
<head>
|
|
577
|
+
<meta charset="UTF-8">
|
|
578
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
579
|
+
<title>Bueno App</title>
|
|
580
|
+
</head>
|
|
581
|
+
<body>
|
|
582
|
+
<div id="root"></div>
|
|
583
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
584
|
+
</body>
|
|
585
|
+
</html>
|
|
586
|
+
`
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
path: "client/src/main.tsx",
|
|
590
|
+
content: `import { StrictMode } from 'react';
|
|
591
|
+
import { createRoot } from 'react-dom/client';
|
|
592
|
+
import { App } from './App';
|
|
593
|
+
import './styles/globals.css';
|
|
594
|
+
|
|
595
|
+
const root = createRoot(document.getElementById('root')!);
|
|
596
|
+
|
|
597
|
+
root.render(
|
|
598
|
+
<StrictMode>
|
|
599
|
+
<App />
|
|
600
|
+
</StrictMode>
|
|
601
|
+
);
|
|
602
|
+
`
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
path: "client/src/App.tsx",
|
|
606
|
+
content: `import { useState } from 'react';
|
|
607
|
+
|
|
608
|
+
export function App() {
|
|
609
|
+
const [count, setCount] = useState(0);
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<main className="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
|
613
|
+
<div className="container mx-auto px-4 py-16">
|
|
614
|
+
<div className="max-w-2xl mx-auto text-center">
|
|
615
|
+
<h1 className="text-5xl font-bold mb-4 bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
|
|
616
|
+
Welcome to Bueno
|
|
617
|
+
</h1>
|
|
618
|
+
<p className="text-xl text-slate-300 mb-8">
|
|
619
|
+
A Bun-native full-stack framework
|
|
620
|
+
</p>
|
|
621
|
+
|
|
622
|
+
<div className="bg-slate-800/50 rounded-xl p-8 backdrop-blur-sm border border-slate-700">
|
|
623
|
+
<button
|
|
624
|
+
onClick={() => setCount(c => c + 1)}
|
|
625
|
+
className="px-6 py-3 bg-blue-500 hover:bg-blue-600 rounded-lg font-medium transition-colors"
|
|
626
|
+
>
|
|
627
|
+
Count: {count}
|
|
628
|
+
</button>
|
|
629
|
+
<p className="mt-4 text-slate-400 text-sm">
|
|
630
|
+
Click the button to test React hydration
|
|
631
|
+
</p>
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<div className="mt-8 grid grid-cols-2 gap-4 text-left">
|
|
635
|
+
<a
|
|
636
|
+
href="https://bueno.github.io"
|
|
637
|
+
className="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-blue-500 transition-colors"
|
|
638
|
+
>
|
|
639
|
+
<h3 className="font-semibold text-blue-400">Documentation</h3>
|
|
640
|
+
<p className="text-sm text-slate-400">Learn more about Bueno</p>
|
|
641
|
+
</a>
|
|
642
|
+
<a
|
|
643
|
+
href="https://github.com/buenojs/bueno"
|
|
644
|
+
className="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-blue-500 transition-colors"
|
|
645
|
+
>
|
|
646
|
+
<h3 className="font-semibold text-blue-400">GitHub</h3>
|
|
647
|
+
<p className="text-sm text-slate-400">View the source code</p>
|
|
648
|
+
</a>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
</main>
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
`
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
path: "client/src/styles/globals.css",
|
|
659
|
+
content: `@tailwind base;
|
|
660
|
+
@tailwind components;
|
|
661
|
+
@tailwind utilities;
|
|
662
|
+
|
|
663
|
+
:root {
|
|
664
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
665
|
+
line-height: 1.5;
|
|
666
|
+
font-weight: 400;
|
|
667
|
+
font-synthesis: none;
|
|
668
|
+
text-rendering: optimizeLegibility;
|
|
669
|
+
-webkit-font-smoothing: antialiased;
|
|
670
|
+
-moz-osx-font-smoothing: grayscale;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
body {
|
|
674
|
+
margin: 0;
|
|
675
|
+
min-width: 320px;
|
|
676
|
+
min-height: 100vh;
|
|
677
|
+
}
|
|
678
|
+
`
|
|
679
|
+
}
|
|
680
|
+
],
|
|
681
|
+
directories: [
|
|
682
|
+
"client/src/components",
|
|
683
|
+
"client/src/styles",
|
|
684
|
+
"client/public"
|
|
685
|
+
],
|
|
686
|
+
dependencies: {
|
|
687
|
+
react: "^18.3.0",
|
|
688
|
+
"react-dom": "^18.3.0"
|
|
689
|
+
},
|
|
690
|
+
devDependencies: {
|
|
691
|
+
"@types/react": "^18.3.0",
|
|
692
|
+
"@types/react-dom": "^18.3.0",
|
|
693
|
+
tailwindcss: "^3.4.0",
|
|
694
|
+
postcss: "^8.4.0",
|
|
695
|
+
autoprefixer: "^10.4.0"
|
|
696
|
+
},
|
|
697
|
+
scripts: {
|
|
698
|
+
"dev:client": "bun run --watch client/index.html",
|
|
699
|
+
"build:client": "bun build ./client/src/main.tsx --outdir ./dist/client"
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/cli/templates/frontend/vue.ts
|
|
705
|
+
function vueTemplate() {
|
|
706
|
+
return {
|
|
707
|
+
files: [
|
|
708
|
+
{
|
|
709
|
+
path: "client/index.html",
|
|
710
|
+
content: `<!DOCTYPE html>
|
|
711
|
+
<html lang="en">
|
|
712
|
+
<head>
|
|
713
|
+
<meta charset="UTF-8">
|
|
714
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
715
|
+
<title>Bueno App</title>
|
|
716
|
+
</head>
|
|
717
|
+
<body>
|
|
718
|
+
<div id="app"></div>
|
|
719
|
+
<script type="module" src="./src/main.ts"></script>
|
|
720
|
+
</body>
|
|
721
|
+
</html>
|
|
722
|
+
`
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
path: "client/src/main.ts",
|
|
726
|
+
content: `import { createApp } from 'vue';
|
|
727
|
+
import App from './App.vue';
|
|
728
|
+
import './styles/globals.css';
|
|
729
|
+
|
|
730
|
+
createApp(App).mount('#app');
|
|
731
|
+
`
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
path: "client/src/App.vue",
|
|
735
|
+
content: `<script setup lang="ts">
|
|
736
|
+
import { ref } from 'vue';
|
|
737
|
+
|
|
738
|
+
const count = ref(0);
|
|
739
|
+
</script>
|
|
740
|
+
|
|
741
|
+
<template>
|
|
742
|
+
<main class="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
|
743
|
+
<div class="container mx-auto px-4 py-16">
|
|
744
|
+
<div class="max-w-2xl mx-auto text-center">
|
|
745
|
+
<h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-green-400 to-emerald-500 bg-clip-text text-transparent">
|
|
746
|
+
Welcome to Bueno
|
|
747
|
+
</h1>
|
|
748
|
+
<p class="text-xl text-slate-300 mb-8">
|
|
749
|
+
A Bun-native full-stack framework
|
|
750
|
+
</p>
|
|
751
|
+
|
|
752
|
+
<div class="bg-slate-800/50 rounded-xl p-8 backdrop-blur-sm border border-slate-700">
|
|
753
|
+
<button
|
|
754
|
+
@click="count++"
|
|
755
|
+
class="px-6 py-3 bg-green-500 hover:bg-green-600 rounded-lg font-medium transition-colors"
|
|
756
|
+
>
|
|
757
|
+
Count: {{ count }}
|
|
758
|
+
</button>
|
|
759
|
+
<p class="mt-4 text-slate-400 text-sm">
|
|
760
|
+
Click the button to test Vue reactivity
|
|
761
|
+
</p>
|
|
762
|
+
</div>
|
|
763
|
+
|
|
764
|
+
<div class="mt-8 grid grid-cols-2 gap-4 text-left">
|
|
765
|
+
<a
|
|
766
|
+
href="https://bueno.github.io"
|
|
767
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-green-500 transition-colors"
|
|
768
|
+
>
|
|
769
|
+
<h3 class="font-semibold text-green-400">Documentation</h3>
|
|
770
|
+
<p class="text-sm text-slate-400">Learn more about Bueno</p>
|
|
771
|
+
</a>
|
|
772
|
+
<a
|
|
773
|
+
href="https://github.com/buenojs/bueno"
|
|
774
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-green-500 transition-colors"
|
|
775
|
+
>
|
|
776
|
+
<h3 class="font-semibold text-green-400">GitHub</h3>
|
|
777
|
+
<p class="text-sm text-slate-400">View the source code</p>
|
|
778
|
+
</a>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
</main>
|
|
783
|
+
</template>
|
|
784
|
+
`
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
path: "client/src/styles/globals.css",
|
|
788
|
+
content: `@tailwind base;
|
|
789
|
+
@tailwind components;
|
|
790
|
+
@tailwind utilities;
|
|
791
|
+
|
|
792
|
+
:root {
|
|
793
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
794
|
+
line-height: 1.5;
|
|
795
|
+
font-weight: 400;
|
|
796
|
+
font-synthesis: none;
|
|
797
|
+
text-rendering: optimizeLegibility;
|
|
798
|
+
-webkit-font-smoothing: antialiased;
|
|
799
|
+
-moz-osx-font-smoothing: grayscale;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
body {
|
|
803
|
+
margin: 0;
|
|
804
|
+
min-width: 320px;
|
|
805
|
+
min-height: 100vh;
|
|
806
|
+
}
|
|
807
|
+
`
|
|
808
|
+
}
|
|
809
|
+
],
|
|
810
|
+
directories: [
|
|
811
|
+
"client/src/components",
|
|
812
|
+
"client/src/styles",
|
|
813
|
+
"client/public"
|
|
814
|
+
],
|
|
815
|
+
dependencies: {
|
|
816
|
+
vue: "^3.4.0"
|
|
817
|
+
},
|
|
818
|
+
devDependencies: {
|
|
819
|
+
tailwindcss: "^3.4.0",
|
|
820
|
+
postcss: "^8.4.0",
|
|
821
|
+
autoprefixer: "^10.4.0"
|
|
822
|
+
},
|
|
823
|
+
scripts: {
|
|
824
|
+
"dev:client": "bun run --watch client/index.html",
|
|
825
|
+
"build:client": "bun build ./client/src/main.ts --outdir ./dist/client"
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/cli/templates/frontend/svelte.ts
|
|
831
|
+
function svelteTemplate() {
|
|
832
|
+
return {
|
|
833
|
+
files: [
|
|
834
|
+
{
|
|
835
|
+
path: "client/index.html",
|
|
836
|
+
content: `<!DOCTYPE html>
|
|
837
|
+
<html lang="en">
|
|
838
|
+
<head>
|
|
839
|
+
<meta charset="UTF-8">
|
|
840
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
841
|
+
<title>Bueno App</title>
|
|
842
|
+
</head>
|
|
843
|
+
<body>
|
|
844
|
+
<div id="app"></div>
|
|
845
|
+
<script type="module" src="./src/main.ts"></script>
|
|
846
|
+
</body>
|
|
847
|
+
</html>
|
|
848
|
+
`
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
path: "client/src/main.ts",
|
|
852
|
+
content: `import App from './App.svelte';
|
|
853
|
+
import './styles/globals.css';
|
|
854
|
+
|
|
855
|
+
const app = new App({
|
|
856
|
+
target: document.getElementById('app')!,
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
export default app;
|
|
860
|
+
`
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
path: "client/src/App.svelte",
|
|
864
|
+
content: `<script lang="ts">
|
|
865
|
+
let count = 0;
|
|
866
|
+
</script>
|
|
867
|
+
|
|
868
|
+
<main class="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
|
869
|
+
<div class="container mx-auto px-4 py-16">
|
|
870
|
+
<div class="max-w-2xl mx-auto text-center">
|
|
871
|
+
<h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-orange-400 to-red-500 bg-clip-text text-transparent">
|
|
872
|
+
Welcome to Bueno
|
|
873
|
+
</h1>
|
|
874
|
+
<p class="text-xl text-slate-300 mb-8">
|
|
875
|
+
A Bun-native full-stack framework
|
|
876
|
+
</p>
|
|
877
|
+
|
|
878
|
+
<div class="bg-slate-800/50 rounded-xl p-8 backdrop-blur-sm border border-slate-700">
|
|
879
|
+
<button
|
|
880
|
+
on:click={() => count++}
|
|
881
|
+
class="px-6 py-3 bg-orange-500 hover:bg-orange-600 rounded-lg font-medium transition-colors"
|
|
882
|
+
>
|
|
883
|
+
Count: {count}
|
|
884
|
+
</button>
|
|
885
|
+
<p class="mt-4 text-slate-400 text-sm">
|
|
886
|
+
Click the button to test Svelte reactivity
|
|
887
|
+
</p>
|
|
888
|
+
</div>
|
|
889
|
+
|
|
890
|
+
<div class="mt-8 grid grid-cols-2 gap-4 text-left">
|
|
891
|
+
<a
|
|
892
|
+
href="https://bueno.github.io"
|
|
893
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-orange-500 transition-colors"
|
|
894
|
+
>
|
|
895
|
+
<h3 class="font-semibold text-orange-400">Documentation</h3>
|
|
896
|
+
<p class="text-sm text-slate-400">Learn more about Bueno</p>
|
|
897
|
+
</a>
|
|
898
|
+
<a
|
|
899
|
+
href="https://github.com/buenojs/bueno"
|
|
900
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-orange-500 transition-colors"
|
|
901
|
+
>
|
|
902
|
+
<h3 class="font-semibold text-orange-400">GitHub</h3>
|
|
903
|
+
<p class="text-sm text-slate-400">View the source code</p>
|
|
904
|
+
</a>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
</div>
|
|
908
|
+
</main>
|
|
909
|
+
`
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
path: "client/src/styles/globals.css",
|
|
913
|
+
content: `@tailwind base;
|
|
914
|
+
@tailwind components;
|
|
915
|
+
@tailwind utilities;
|
|
916
|
+
|
|
917
|
+
:root {
|
|
918
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
919
|
+
line-height: 1.5;
|
|
920
|
+
font-weight: 400;
|
|
921
|
+
font-synthesis: none;
|
|
922
|
+
text-rendering: optimizeLegibility;
|
|
923
|
+
-webkit-font-smoothing: antialiased;
|
|
924
|
+
-moz-osx-font-smoothing: grayscale;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
body {
|
|
928
|
+
margin: 0;
|
|
929
|
+
min-width: 320px;
|
|
930
|
+
min-height: 100vh;
|
|
931
|
+
}
|
|
932
|
+
`
|
|
933
|
+
}
|
|
934
|
+
],
|
|
935
|
+
directories: [
|
|
936
|
+
"client/src/components",
|
|
937
|
+
"client/src/styles",
|
|
938
|
+
"client/public"
|
|
939
|
+
],
|
|
940
|
+
dependencies: {
|
|
941
|
+
svelte: "^4.2.0"
|
|
942
|
+
},
|
|
943
|
+
devDependencies: {
|
|
944
|
+
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
|
945
|
+
svelte: "^4.2.0",
|
|
946
|
+
tailwindcss: "^3.4.0",
|
|
947
|
+
postcss: "^8.4.0",
|
|
948
|
+
autoprefixer: "^10.4.0"
|
|
949
|
+
},
|
|
950
|
+
scripts: {
|
|
951
|
+
"dev:client": "bun run --watch client/index.html",
|
|
952
|
+
"build:client": "bun build ./client/src/main.ts --outdir ./dist/client"
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/cli/templates/frontend/solid.ts
|
|
958
|
+
function solidTemplate() {
|
|
959
|
+
return {
|
|
960
|
+
files: [
|
|
961
|
+
{
|
|
962
|
+
path: "client/index.html",
|
|
963
|
+
content: `<!DOCTYPE html>
|
|
964
|
+
<html lang="en">
|
|
965
|
+
<head>
|
|
966
|
+
<meta charset="UTF-8">
|
|
967
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
968
|
+
<title>Bueno App</title>
|
|
969
|
+
</head>
|
|
970
|
+
<body>
|
|
971
|
+
<div id="root"></div>
|
|
972
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
973
|
+
</body>
|
|
974
|
+
</html>
|
|
975
|
+
`
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
path: "client/src/main.tsx",
|
|
979
|
+
content: `import { render } from 'solid-js/web';
|
|
980
|
+
import { App } from './App';
|
|
981
|
+
import './styles/globals.css';
|
|
982
|
+
|
|
983
|
+
const root = document.getElementById('root');
|
|
984
|
+
|
|
985
|
+
if (root) {
|
|
986
|
+
render(() => <App />, root);
|
|
987
|
+
}
|
|
988
|
+
`
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
path: "client/src/App.tsx",
|
|
992
|
+
content: `import { createSignal } from 'solid-js';
|
|
993
|
+
|
|
994
|
+
export function App() {
|
|
995
|
+
const [count, setCount] = createSignal(0);
|
|
996
|
+
|
|
997
|
+
return (
|
|
998
|
+
<main class="min-h-screen bg-gradient-to-br from-slate-900 to-slate-800 text-white">
|
|
999
|
+
<div class="container mx-auto px-4 py-16">
|
|
1000
|
+
<div class="max-w-2xl mx-auto text-center">
|
|
1001
|
+
<h1 class="text-5xl font-bold mb-4 bg-gradient-to-r from-cyan-400 to-blue-500 bg-clip-text text-transparent">
|
|
1002
|
+
Welcome to Bueno
|
|
1003
|
+
</h1>
|
|
1004
|
+
<p class="text-xl text-slate-300 mb-8">
|
|
1005
|
+
A Bun-native full-stack framework
|
|
1006
|
+
</p>
|
|
1007
|
+
|
|
1008
|
+
<div class="bg-slate-800/50 rounded-xl p-8 backdrop-blur-sm border border-slate-700">
|
|
1009
|
+
<button
|
|
1010
|
+
onClick={() => setCount(c => c + 1)}
|
|
1011
|
+
class="px-6 py-3 bg-cyan-500 hover:bg-cyan-600 rounded-lg font-medium transition-colors"
|
|
1012
|
+
>
|
|
1013
|
+
Count: {count()}
|
|
1014
|
+
</button>
|
|
1015
|
+
<p class="mt-4 text-slate-400 text-sm">
|
|
1016
|
+
Click the button to test Solid reactivity
|
|
1017
|
+
</p>
|
|
1018
|
+
</div>
|
|
1019
|
+
|
|
1020
|
+
<div class="mt-8 grid grid-cols-2 gap-4 text-left">
|
|
1021
|
+
<a
|
|
1022
|
+
href="https://bueno.github.io"
|
|
1023
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-cyan-500 transition-colors"
|
|
1024
|
+
>
|
|
1025
|
+
<h3 class="font-semibold text-cyan-400">Documentation</h3>
|
|
1026
|
+
<p class="text-sm text-slate-400">Learn more about Bueno</p>
|
|
1027
|
+
</a>
|
|
1028
|
+
<a
|
|
1029
|
+
href="https://github.com/buenojs/bueno"
|
|
1030
|
+
class="p-4 bg-slate-800/50 rounded-lg border border-slate-700 hover:border-cyan-500 transition-colors"
|
|
1031
|
+
>
|
|
1032
|
+
<h3 class="font-semibold text-cyan-400">GitHub</h3>
|
|
1033
|
+
<p class="text-sm text-slate-400">View the source code</p>
|
|
1034
|
+
</a>
|
|
1035
|
+
</div>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</main>
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
`
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
path: "client/src/styles/globals.css",
|
|
1045
|
+
content: `@tailwind base;
|
|
1046
|
+
@tailwind components;
|
|
1047
|
+
@tailwind utilities;
|
|
1048
|
+
|
|
1049
|
+
:root {
|
|
1050
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
1051
|
+
line-height: 1.5;
|
|
1052
|
+
font-weight: 400;
|
|
1053
|
+
font-synthesis: none;
|
|
1054
|
+
text-rendering: optimizeLegibility;
|
|
1055
|
+
-webkit-font-smoothing: antialiased;
|
|
1056
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
body {
|
|
1060
|
+
margin: 0;
|
|
1061
|
+
min-width: 320px;
|
|
1062
|
+
min-height: 100vh;
|
|
1063
|
+
}
|
|
1064
|
+
`
|
|
1065
|
+
}
|
|
1066
|
+
],
|
|
1067
|
+
directories: [
|
|
1068
|
+
"client/src/components",
|
|
1069
|
+
"client/src/styles",
|
|
1070
|
+
"client/public"
|
|
1071
|
+
],
|
|
1072
|
+
dependencies: {
|
|
1073
|
+
"solid-js": "^1.8.0"
|
|
1074
|
+
},
|
|
1075
|
+
devDependencies: {
|
|
1076
|
+
tailwindcss: "^3.4.0",
|
|
1077
|
+
postcss: "^8.4.0",
|
|
1078
|
+
autoprefixer: "^10.4.0"
|
|
1079
|
+
},
|
|
1080
|
+
scripts: {
|
|
1081
|
+
"dev:client": "bun run --watch client/index.html",
|
|
1082
|
+
"build:client": "bun build ./client/src/main.tsx --outdir ./dist/client"
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/cli/templates/frontend/none.ts
|
|
1088
|
+
function noneTemplate() {
|
|
1089
|
+
return {
|
|
1090
|
+
files: [],
|
|
1091
|
+
directories: [],
|
|
1092
|
+
dependencies: {},
|
|
1093
|
+
devDependencies: {},
|
|
1094
|
+
scripts: {}
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/cli/templates/project/default.ts
|
|
1099
|
+
function defaultTemplate(config) {
|
|
1100
|
+
const frontendTemplates = {
|
|
1101
|
+
react: reactTemplate,
|
|
1102
|
+
vue: vueTemplate,
|
|
1103
|
+
svelte: svelteTemplate,
|
|
1104
|
+
solid: solidTemplate,
|
|
1105
|
+
none: noneTemplate
|
|
1106
|
+
};
|
|
1107
|
+
const frontendTemplate = frontendTemplates[config.framework] ? frontendTemplates[config.framework]() : reactTemplate();
|
|
1108
|
+
return {
|
|
1109
|
+
files: [
|
|
1110
|
+
{
|
|
1111
|
+
path: "server/main.ts",
|
|
1112
|
+
content: `import { createApp, Module, Controller, Get, Injectable } from '@buenojs/bueno';
|
|
1113
|
+
import type { Context } from '@buenojs/bueno';
|
|
1114
|
+
|
|
1115
|
+
// Services
|
|
1116
|
+
@Injectable()
|
|
1117
|
+
export class AppService {
|
|
1118
|
+
findAll() {
|
|
1119
|
+
return { message: 'Welcome to Bueno!', items: [] };
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Controllers
|
|
1124
|
+
@Controller()
|
|
1125
|
+
export class AppController {
|
|
1126
|
+
constructor(private readonly appService: AppService) {}
|
|
1127
|
+
|
|
1128
|
+
@Get()
|
|
1129
|
+
hello() {
|
|
1130
|
+
return new Response(\`<html>
|
|
1131
|
+
<head>
|
|
1132
|
+
<title>Welcome to Bueno</title>
|
|
1133
|
+
<style>
|
|
1134
|
+
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
|
1135
|
+
h1 { color: #2563eb; }
|
|
1136
|
+
code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; }
|
|
1137
|
+
pre { background: #f3f4f6; padding: 16px; border-radius: 8px; overflow-x: auto; }
|
|
1138
|
+
a { color: #2563eb; }
|
|
1139
|
+
</style>
|
|
1140
|
+
</head>
|
|
1141
|
+
<body>
|
|
1142
|
+
<h1>\uD83C\uDF89 Welcome to Bueno Framework!</h1>
|
|
1143
|
+
<p>Your Bun-native full-stack framework is running successfully.</p>
|
|
1144
|
+
|
|
1145
|
+
<h2>Getting Started</h2>
|
|
1146
|
+
<ul>
|
|
1147
|
+
<li>Edit <code>server/main.ts</code> to modify this app</li>
|
|
1148
|
+
<li>Add routes using the <code>@Get()</code>, <code>@Post()</code> decorators</li>
|
|
1149
|
+
<li>Create services with <code>@Injectable()</code> and inject them in controllers</li>
|
|
1150
|
+
</ul>
|
|
1151
|
+
|
|
1152
|
+
<h2>Documentation</h2>
|
|
1153
|
+
<p>Visit <a href="https://buenojs.dev">https://buenojs.dev</a> for full documentation.</p>
|
|
1154
|
+
|
|
1155
|
+
<h2>Quick Example</h2>
|
|
1156
|
+
<pre><code>@Controller('/api')
|
|
1157
|
+
class MyController {
|
|
1158
|
+
@Get('/users')
|
|
1159
|
+
getUsers() {
|
|
1160
|
+
return { users: [] };
|
|
1161
|
+
}
|
|
1162
|
+
}</code></pre>
|
|
1163
|
+
</body>
|
|
1164
|
+
</html>\`, {
|
|
1165
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
@Get('health')
|
|
1170
|
+
health(ctx: Context) {
|
|
1171
|
+
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Module
|
|
1176
|
+
@Module({
|
|
1177
|
+
controllers: [AppController],
|
|
1178
|
+
providers: [AppService],
|
|
1179
|
+
})
|
|
1180
|
+
export class AppModule {}
|
|
1181
|
+
|
|
1182
|
+
// Bootstrap
|
|
1183
|
+
const app = createApp(AppModule);
|
|
1184
|
+
await app.listen(3000);
|
|
1185
|
+
`
|
|
1186
|
+
},
|
|
1187
|
+
...frontendTemplate.files
|
|
1188
|
+
],
|
|
1189
|
+
directories: [
|
|
1190
|
+
"server/modules/app",
|
|
1191
|
+
"server/common/middleware",
|
|
1192
|
+
"server/common/guards",
|
|
1193
|
+
"server/common/interceptors",
|
|
1194
|
+
"server/common/pipes",
|
|
1195
|
+
"server/common/filters",
|
|
1196
|
+
"server/database/migrations",
|
|
1197
|
+
"server/config",
|
|
1198
|
+
"tests/unit",
|
|
1199
|
+
"tests/integration",
|
|
1200
|
+
...frontendTemplate.directories
|
|
1201
|
+
],
|
|
1202
|
+
dependencies: {
|
|
1203
|
+
zod: "^3.24.0",
|
|
1204
|
+
...frontendTemplate.dependencies
|
|
1205
|
+
},
|
|
1206
|
+
devDependencies: {
|
|
1207
|
+
...frontendTemplate.devDependencies
|
|
1208
|
+
},
|
|
1209
|
+
scripts: {
|
|
1210
|
+
dev: "bun run --watch server/main.ts",
|
|
1211
|
+
build: "bun build ./server/main.ts --outdir ./dist --target bun",
|
|
1212
|
+
start: "bun run dist/main.js",
|
|
1213
|
+
test: "bun test",
|
|
1214
|
+
...frontendTemplate.scripts
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
var init_default = () => {};
|
|
1219
|
+
|
|
1220
|
+
// src/cli/templates/project/minimal.ts
|
|
1221
|
+
function minimalTemplate(config) {
|
|
1222
|
+
return {
|
|
1223
|
+
files: [
|
|
1224
|
+
{
|
|
1225
|
+
path: "server/main.ts",
|
|
1226
|
+
content: `import { createServer } from '@buenojs/bueno';
|
|
1227
|
+
|
|
1228
|
+
const app = createServer();
|
|
1229
|
+
|
|
1230
|
+
app.router.get('/', () => {
|
|
1231
|
+
return { message: 'Hello, Bueno!' };
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
await app.listen(3000);
|
|
1235
|
+
`
|
|
1236
|
+
}
|
|
1237
|
+
],
|
|
1238
|
+
directories: ["server", "tests"],
|
|
1239
|
+
dependencies: {
|
|
1240
|
+
...getBuenoDependency()
|
|
1241
|
+
},
|
|
1242
|
+
devDependencies: {
|
|
1243
|
+
"@types/bun": "latest",
|
|
1244
|
+
typescript: "^5.3.0"
|
|
1245
|
+
},
|
|
1246
|
+
scripts: {
|
|
1247
|
+
dev: "bun run --watch server/main.ts",
|
|
1248
|
+
build: "bun build ./server/main.ts --outdir ./dist --target bun",
|
|
1249
|
+
start: "bun run dist/main.js",
|
|
1250
|
+
test: "bun test"
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
var init_minimal = __esm(() => {
|
|
1255
|
+
init_version();
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
// src/cli/templates/project/fullstack.ts
|
|
1259
|
+
function fullstackTemplate(config) {
|
|
1260
|
+
const frontendTemplates = {
|
|
1261
|
+
react: reactTemplate,
|
|
1262
|
+
vue: vueTemplate,
|
|
1263
|
+
svelte: svelteTemplate,
|
|
1264
|
+
solid: solidTemplate,
|
|
1265
|
+
none: noneTemplate
|
|
1266
|
+
};
|
|
1267
|
+
const frontendTemplate = frontendTemplates[config.framework] ? frontendTemplates[config.framework]() : reactTemplate();
|
|
1268
|
+
return {
|
|
1269
|
+
files: [
|
|
1270
|
+
{
|
|
1271
|
+
path: "server/main.ts",
|
|
1272
|
+
content: `import { createApp, Module, Controller, Get, Post, Injectable } from '@buenojs/bueno';
|
|
1273
|
+
import type { Context } from '@buenojs/bueno';
|
|
1274
|
+
|
|
1275
|
+
// Services
|
|
1276
|
+
@Injectable()
|
|
1277
|
+
export class AppService {
|
|
1278
|
+
findAll() {
|
|
1279
|
+
return { message: 'Welcome to Bueno!', items: [] };
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Controllers
|
|
1284
|
+
@Controller('/api')
|
|
1285
|
+
export class AppController {
|
|
1286
|
+
constructor(private readonly appService: AppService) {}
|
|
1287
|
+
|
|
1288
|
+
@Get()
|
|
1289
|
+
hello() {
|
|
1290
|
+
return { message: 'Welcome to Bueno API!', version: '1.0.0' };
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
@Get('health')
|
|
1294
|
+
health(ctx: Context) {
|
|
1295
|
+
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Module
|
|
1300
|
+
@Module({
|
|
1301
|
+
controllers: [AppController],
|
|
1302
|
+
providers: [AppService],
|
|
1303
|
+
})
|
|
1304
|
+
export class AppModule {}
|
|
1305
|
+
|
|
1306
|
+
// Bootstrap
|
|
1307
|
+
const app = createApp(AppModule);
|
|
1308
|
+
|
|
1309
|
+
// Serve static files in production
|
|
1310
|
+
// app.serveStatic('./dist/client');
|
|
1311
|
+
|
|
1312
|
+
await app.listen(3000);
|
|
1313
|
+
console.log('\uD83D\uDE80 Server running at http://localhost:3000');
|
|
1314
|
+
`
|
|
1315
|
+
},
|
|
1316
|
+
...frontendTemplate.files
|
|
1317
|
+
],
|
|
1318
|
+
directories: [
|
|
1319
|
+
"server/modules/app",
|
|
1320
|
+
"server/common/middleware",
|
|
1321
|
+
"server/common/guards",
|
|
1322
|
+
"server/common/interceptors",
|
|
1323
|
+
"server/common/pipes",
|
|
1324
|
+
"server/common/filters",
|
|
1325
|
+
"server/database/migrations",
|
|
1326
|
+
"server/config",
|
|
1327
|
+
"tests/unit",
|
|
1328
|
+
"tests/integration",
|
|
1329
|
+
...frontendTemplate.directories
|
|
1330
|
+
],
|
|
1331
|
+
dependencies: {
|
|
1332
|
+
zod: "^3.24.0",
|
|
1333
|
+
...frontendTemplate.dependencies
|
|
1334
|
+
},
|
|
1335
|
+
devDependencies: {
|
|
1336
|
+
...frontendTemplate.devDependencies
|
|
1337
|
+
},
|
|
1338
|
+
scripts: {
|
|
1339
|
+
dev: "bun run --watch server/main.ts",
|
|
1340
|
+
build: "bun build ./server/main.ts --outdir ./dist --target bun",
|
|
1341
|
+
start: "bun run dist/main.js",
|
|
1342
|
+
test: "bun test",
|
|
1343
|
+
...frontendTemplate.scripts
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
217
1346
|
}
|
|
218
|
-
|
|
219
|
-
|
|
1347
|
+
var init_fullstack = () => {};
|
|
1348
|
+
|
|
1349
|
+
// src/cli/templates/project/api.ts
|
|
1350
|
+
function apiTemplate(config) {
|
|
1351
|
+
return {
|
|
1352
|
+
files: [
|
|
1353
|
+
{
|
|
1354
|
+
path: "server/main.ts",
|
|
1355
|
+
content: `import { createApp, Module, Controller, Get, Post, Injectable } from '@buenojs/bueno';
|
|
1356
|
+
import type { Context } from '@buenojs/bueno';
|
|
1357
|
+
|
|
1358
|
+
// Services
|
|
1359
|
+
@Injectable()
|
|
1360
|
+
export class AppService {
|
|
1361
|
+
findAll() {
|
|
1362
|
+
return { message: 'Welcome to Bueno API!', items: [] };
|
|
1363
|
+
}
|
|
220
1364
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1365
|
+
|
|
1366
|
+
// Controllers
|
|
1367
|
+
@Controller()
|
|
1368
|
+
export class AppController {
|
|
1369
|
+
constructor(private readonly appService: AppService) {}
|
|
1370
|
+
|
|
1371
|
+
@Get()
|
|
1372
|
+
hello() {
|
|
1373
|
+
return { message: 'Welcome to Bueno API!', version: '1.0.0' };
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
@Get('health')
|
|
1377
|
+
health(ctx: Context) {
|
|
1378
|
+
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
1379
|
+
}
|
|
225
1380
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
1381
|
+
|
|
1382
|
+
// Module
|
|
1383
|
+
@Module({
|
|
1384
|
+
controllers: [AppController],
|
|
1385
|
+
providers: [AppService],
|
|
1386
|
+
})
|
|
1387
|
+
export class AppModule {}
|
|
1388
|
+
|
|
1389
|
+
// Bootstrap
|
|
1390
|
+
const app = createApp(AppModule);
|
|
1391
|
+
await app.listen(3000);
|
|
1392
|
+
`
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
directories: [
|
|
1396
|
+
"server/modules/app",
|
|
1397
|
+
"server/common/middleware",
|
|
1398
|
+
"server/common/guards",
|
|
1399
|
+
"server/common/interceptors",
|
|
1400
|
+
"server/common/pipes",
|
|
1401
|
+
"server/common/filters",
|
|
1402
|
+
"server/database/migrations",
|
|
1403
|
+
"server/config",
|
|
1404
|
+
"tests/unit",
|
|
1405
|
+
"tests/integration"
|
|
1406
|
+
],
|
|
1407
|
+
dependencies: {
|
|
1408
|
+
...getBuenoDependency(),
|
|
1409
|
+
zod: "^3.24.0"
|
|
1410
|
+
},
|
|
1411
|
+
devDependencies: {
|
|
1412
|
+
"@types/bun": "latest",
|
|
1413
|
+
typescript: "^5.3.0"
|
|
1414
|
+
},
|
|
1415
|
+
scripts: {
|
|
1416
|
+
dev: "bun run --watch server/main.ts",
|
|
1417
|
+
build: "bun build ./server/main.ts --outdir ./dist --target bun",
|
|
1418
|
+
start: "bun run dist/main.js",
|
|
1419
|
+
test: "bun test"
|
|
263
1420
|
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
var init_api = __esm(() => {
|
|
1424
|
+
init_version();
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// src/cli/templates/project/website.ts
|
|
1428
|
+
function websiteTemplate(config) {
|
|
1429
|
+
return {
|
|
1430
|
+
files: [
|
|
1431
|
+
{
|
|
1432
|
+
path: "src/build.ts",
|
|
1433
|
+
content: `/**
|
|
1434
|
+
* Build script for ${config.name}
|
|
1435
|
+
*
|
|
1436
|
+
* Uses Bueno's SSG module to generate static HTML from markdown content
|
|
1437
|
+
*/
|
|
1438
|
+
|
|
1439
|
+
import { SSG, createSSG, type SiteConfig, type LayoutContext } from 'bueno';
|
|
1440
|
+
|
|
1441
|
+
// Site configuration
|
|
1442
|
+
const siteConfig: Partial<SiteConfig> = {
|
|
1443
|
+
title: '${config.name}',
|
|
1444
|
+
description: 'A static website built with Bueno',
|
|
1445
|
+
baseUrl: '/',
|
|
280
1446
|
};
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
1447
|
+
|
|
1448
|
+
// Create SSG instance
|
|
1449
|
+
const ssg = createSSG(
|
|
1450
|
+
{
|
|
1451
|
+
contentDir: './content',
|
|
1452
|
+
outputDir: './dist',
|
|
1453
|
+
publicDir: './public',
|
|
1454
|
+
defaultLayout: 'default',
|
|
1455
|
+
},
|
|
1456
|
+
siteConfig
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
// Register custom layouts
|
|
1460
|
+
ssg.registerLayout('default', renderDefaultLayout);
|
|
1461
|
+
|
|
1462
|
+
// Build the site
|
|
1463
|
+
console.log('Building website...');
|
|
1464
|
+
await ssg.build();
|
|
1465
|
+
console.log('Build complete!');
|
|
1466
|
+
|
|
1467
|
+
// ============= Layout Templates =============
|
|
1468
|
+
|
|
1469
|
+
function renderDefaultLayout(ctx: LayoutContext): string {
|
|
1470
|
+
const title = ctx.page.frontmatter.title || ctx.site.title;
|
|
1471
|
+
const description = ctx.page.frontmatter.description || ctx.site.description;
|
|
1472
|
+
|
|
1473
|
+
return \`<!DOCTYPE html>
|
|
1474
|
+
<html lang="en">
|
|
1475
|
+
<head>
|
|
1476
|
+
<meta charset="UTF-8">
|
|
1477
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1478
|
+
<title>\${title}</title>
|
|
1479
|
+
<meta name="description" content="\${description}">
|
|
1480
|
+
<link rel="stylesheet" href="\${ctx.site.baseUrl}styles/main.css">
|
|
1481
|
+
</head>
|
|
1482
|
+
<body>
|
|
1483
|
+
<header>
|
|
1484
|
+
<nav>
|
|
1485
|
+
<a href="\${ctx.site.baseUrl}">Home</a>
|
|
1486
|
+
<a href="\${ctx.site.baseUrl}about">About</a>
|
|
1487
|
+
</nav>
|
|
1488
|
+
</header>
|
|
1489
|
+
<main>
|
|
1490
|
+
\${ctx.content}
|
|
1491
|
+
</main>
|
|
1492
|
+
<footer>
|
|
1493
|
+
<p>© \${new Date().getFullYear()} \${ctx.site.title}</p>
|
|
1494
|
+
</footer>
|
|
1495
|
+
</body>
|
|
1496
|
+
</html>\`;
|
|
1497
|
+
}
|
|
1498
|
+
`
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
path: "src/serve.ts",
|
|
1502
|
+
content: `/**
|
|
1503
|
+
* Development server for serving the built website
|
|
1504
|
+
*/
|
|
1505
|
+
|
|
1506
|
+
const PORT = 3001;
|
|
1507
|
+
|
|
1508
|
+
async function serve() {
|
|
1509
|
+
console.log(\`Starting server at http://localhost:\${PORT}\`);
|
|
1510
|
+
|
|
1511
|
+
Bun.serve({
|
|
1512
|
+
port: PORT,
|
|
1513
|
+
async fetch(request) {
|
|
1514
|
+
const url = new URL(request.url);
|
|
1515
|
+
let path = url.pathname;
|
|
1516
|
+
|
|
1517
|
+
// Serve index.html for root
|
|
1518
|
+
if (path === '/') {
|
|
1519
|
+
path = '/index.html';
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Try to serve from dist directory
|
|
1523
|
+
const filePath = \`./dist\${path}\`;
|
|
1524
|
+
const file = Bun.file(filePath);
|
|
1525
|
+
|
|
1526
|
+
if (await file.exists()) {
|
|
1527
|
+
return new Response(file);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// For SPA-like behavior, try adding .html extension
|
|
1531
|
+
if (!path.includes('.')) {
|
|
1532
|
+
const htmlPath = \`./dist\${path}/index.html\`;
|
|
1533
|
+
const htmlFile = Bun.file(htmlPath);
|
|
1534
|
+
|
|
1535
|
+
if (await htmlFile.exists()) {
|
|
1536
|
+
return new Response(htmlFile);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// 404
|
|
1541
|
+
return new Response('Not Found', { status: 404 });
|
|
1542
|
+
},
|
|
286
1543
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
1544
|
+
|
|
1545
|
+
console.log(\`Server running at http://localhost:\${PORT}\`);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
serve();
|
|
1549
|
+
`
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
path: "content/index.md",
|
|
1553
|
+
content: `---
|
|
1554
|
+
title: Welcome
|
|
1555
|
+
description: Welcome to my website
|
|
1556
|
+
layout: default
|
|
1557
|
+
---
|
|
1558
|
+
|
|
1559
|
+
# Welcome to ${config.name}
|
|
1560
|
+
|
|
1561
|
+
This is a static website built with [Bueno Framework](https://buenojs.dev).
|
|
1562
|
+
|
|
1563
|
+
## Getting Started
|
|
1564
|
+
|
|
1565
|
+
1. Edit content in the \`content/\` directory
|
|
1566
|
+
2. Run \`bun run dev\` to start development
|
|
1567
|
+
3. Run \`bun run build\` to generate static files
|
|
1568
|
+
`
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
path: "public/styles/main.css",
|
|
1572
|
+
content: `/* Main styles for the website */
|
|
1573
|
+
* {
|
|
1574
|
+
box-sizing: border-box;
|
|
1575
|
+
margin: 0;
|
|
1576
|
+
padding: 0;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
body {
|
|
1580
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
1581
|
+
line-height: 1.6;
|
|
1582
|
+
color: #333;
|
|
1583
|
+
max-width: 800px;
|
|
1584
|
+
margin: 0 auto;
|
|
1585
|
+
padding: 20px;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
header {
|
|
1589
|
+
padding: 20px 0;
|
|
1590
|
+
border-bottom: 1px solid #eee;
|
|
1591
|
+
margin-bottom: 20px;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
nav {
|
|
1595
|
+
display: flex;
|
|
1596
|
+
gap: 20px;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
nav a {
|
|
1600
|
+
color: #2563eb;
|
|
1601
|
+
text-decoration: none;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
nav a:hover {
|
|
1605
|
+
text-decoration: underline;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
main {
|
|
1609
|
+
min-height: 60vh;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
footer {
|
|
1613
|
+
padding: 20px 0;
|
|
1614
|
+
border-top: 1px solid #eee;
|
|
1615
|
+
margin-top: 40px;
|
|
1616
|
+
text-align: center;
|
|
1617
|
+
color: #666;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
h1 {
|
|
1621
|
+
color: #2563eb;
|
|
1622
|
+
margin-bottom: 20px;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
h2 {
|
|
1626
|
+
margin-top: 30px;
|
|
1627
|
+
margin-bottom: 15px;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
p {
|
|
1631
|
+
margin-bottom: 15px;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
ul, ol {
|
|
1635
|
+
margin-left: 20px;
|
|
1636
|
+
margin-bottom: 15px;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
code {
|
|
1640
|
+
background: #f3f4f6;
|
|
1641
|
+
padding: 2px 6px;
|
|
1642
|
+
border-radius: 4px;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
pre {
|
|
1646
|
+
background: #f3f4f6;
|
|
1647
|
+
padding: 16px;
|
|
1648
|
+
border-radius: 8px;
|
|
1649
|
+
overflow-x: auto;
|
|
1650
|
+
margin-bottom: 15px;
|
|
1651
|
+
}
|
|
1652
|
+
`
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
path: ".env.example",
|
|
1656
|
+
content: `# ${config.name} Environment Variables
|
|
1657
|
+
# Copy this file to .env and customize as needed
|
|
1658
|
+
|
|
1659
|
+
# Site Configuration
|
|
1660
|
+
SITE_URL=http://localhost:3001
|
|
1661
|
+
|
|
1662
|
+
# Build Configuration
|
|
1663
|
+
NODE_ENV=development
|
|
1664
|
+
`
|
|
1665
|
+
}
|
|
1666
|
+
],
|
|
1667
|
+
directories: ["src", "content", "public/styles", "layouts"],
|
|
1668
|
+
dependencies: {
|
|
1669
|
+
...getBuenoDependency()
|
|
1670
|
+
},
|
|
1671
|
+
devDependencies: {
|
|
1672
|
+
"@types/bun": "latest",
|
|
1673
|
+
typescript: "^5.3.0"
|
|
1674
|
+
},
|
|
1675
|
+
scripts: {
|
|
1676
|
+
dev: "bun run --watch src/build.ts --dev",
|
|
1677
|
+
build: "bun run src/build.ts",
|
|
1678
|
+
serve: "bun run src/serve.ts"
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
var init_website = __esm(() => {
|
|
1683
|
+
init_version();
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
// src/cli/templates/project/index.ts
|
|
1687
|
+
function getProjectTemplate(template) {
|
|
1688
|
+
return projectTemplates[template];
|
|
1689
|
+
}
|
|
1690
|
+
function getTemplateOptions() {
|
|
291
1691
|
return [
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
1692
|
+
{ value: "default", name: "Default", description: "Standard project with modules and database" },
|
|
1693
|
+
{ value: "minimal", name: "Minimal", description: "Bare minimum project structure" },
|
|
1694
|
+
{ value: "fullstack", name: "Fullstack", description: "Full-stack project with SSR and frontend" },
|
|
1695
|
+
{ value: "api", name: "API", description: "API-only project without frontend" },
|
|
1696
|
+
{ value: "website", name: "Website", description: "Static website with SSG" }
|
|
1697
|
+
];
|
|
297
1698
|
}
|
|
298
|
-
|
|
299
|
-
|
|
1699
|
+
var projectTemplates;
|
|
1700
|
+
var init_project = __esm(() => {
|
|
1701
|
+
init_default();
|
|
1702
|
+
init_minimal();
|
|
1703
|
+
init_fullstack();
|
|
1704
|
+
init_api();
|
|
1705
|
+
init_website();
|
|
1706
|
+
projectTemplates = {
|
|
1707
|
+
default: defaultTemplate,
|
|
1708
|
+
minimal: minimalTemplate,
|
|
1709
|
+
fullstack: fullstackTemplate,
|
|
1710
|
+
api: apiTemplate,
|
|
1711
|
+
website: websiteTemplate
|
|
1712
|
+
};
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
// src/cli/templates/database/sqlite.ts
|
|
1716
|
+
function sqliteTemplate() {
|
|
1717
|
+
return {
|
|
1718
|
+
files: [],
|
|
1719
|
+
directories: [],
|
|
1720
|
+
envConfig: "DATABASE_URL=sqlite:./data.db",
|
|
1721
|
+
configCode: `{ url: 'sqlite:./data.db' }`
|
|
1722
|
+
};
|
|
300
1723
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1724
|
+
|
|
1725
|
+
// src/cli/templates/database/postgresql.ts
|
|
1726
|
+
function postgresqlTemplate() {
|
|
1727
|
+
return {
|
|
1728
|
+
files: [],
|
|
1729
|
+
directories: [],
|
|
1730
|
+
envConfig: "DATABASE_URL=postgresql://user:password@localhost:5432/dbname",
|
|
1731
|
+
configCode: `{ url: process.env.DATABASE_URL ?? 'postgresql://localhost/dbname' }`
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/cli/templates/database/mysql.ts
|
|
1736
|
+
function mysqlTemplate() {
|
|
1737
|
+
return {
|
|
1738
|
+
files: [],
|
|
1739
|
+
directories: [],
|
|
1740
|
+
envConfig: "DATABASE_URL=mysql://user:password@localhost:3306/dbname",
|
|
1741
|
+
configCode: `{ url: process.env.DATABASE_URL ?? 'mysql://localhost/dbname' }`
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/cli/templates/database/none.ts
|
|
1746
|
+
function noneTemplate2() {
|
|
1747
|
+
return {
|
|
1748
|
+
files: [],
|
|
1749
|
+
directories: [],
|
|
1750
|
+
envConfig: undefined,
|
|
1751
|
+
configCode: undefined
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/cli/templates/database/index.ts
|
|
1756
|
+
function getDatabaseTemplate(driver) {
|
|
1757
|
+
return databaseTemplates[driver]();
|
|
1758
|
+
}
|
|
1759
|
+
function getDatabaseOptions() {
|
|
1760
|
+
return [
|
|
1761
|
+
{ value: "none", name: "None", description: "No database required" },
|
|
1762
|
+
{ value: "sqlite", name: "SQLite", description: "Local file-based database" },
|
|
1763
|
+
{ value: "postgresql", name: "PostgreSQL", description: "Production-ready relational database" },
|
|
1764
|
+
{ value: "mysql", name: "MySQL", description: "Popular relational database" }
|
|
1765
|
+
];
|
|
1766
|
+
}
|
|
1767
|
+
var databaseTemplates;
|
|
1768
|
+
var init_database = __esm(() => {
|
|
1769
|
+
databaseTemplates = {
|
|
1770
|
+
sqlite: sqliteTemplate,
|
|
1771
|
+
postgresql: postgresqlTemplate,
|
|
1772
|
+
mysql: mysqlTemplate,
|
|
1773
|
+
none: noneTemplate2
|
|
1774
|
+
};
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
// src/cli/templates/frontend/index.ts
|
|
1778
|
+
function getFrontendTemplate(framework) {
|
|
1779
|
+
return frontendTemplates[framework]();
|
|
1780
|
+
}
|
|
1781
|
+
function getFrontendOptions() {
|
|
1782
|
+
return [
|
|
1783
|
+
{ value: "none", name: "None", description: "Static website or API only" },
|
|
1784
|
+
{ value: "react", name: "React", description: "React with SSR support" },
|
|
1785
|
+
{ value: "vue", name: "Vue", description: "Vue 3 with SSR support" },
|
|
1786
|
+
{ value: "svelte", name: "Svelte", description: "Svelte with SSR support" },
|
|
1787
|
+
{ value: "solid", name: "Solid", description: "SolidJS with SSR support" }
|
|
1788
|
+
];
|
|
1789
|
+
}
|
|
1790
|
+
var frontendTemplates;
|
|
1791
|
+
var init_frontend = __esm(() => {
|
|
1792
|
+
frontendTemplates = {
|
|
1793
|
+
react: reactTemplate,
|
|
1794
|
+
vue: vueTemplate,
|
|
1795
|
+
svelte: svelteTemplate,
|
|
1796
|
+
solid: solidTemplate,
|
|
1797
|
+
none: noneTemplate
|
|
1798
|
+
};
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
// src/cli/templates/generators/types.ts
|
|
1802
|
+
var GENERATOR_ALIASES;
|
|
1803
|
+
var init_types = __esm(() => {
|
|
1804
|
+
GENERATOR_ALIASES = {
|
|
1805
|
+
c: "controller",
|
|
1806
|
+
s: "service",
|
|
1807
|
+
m: "module",
|
|
1808
|
+
gu: "guard",
|
|
1809
|
+
i: "interceptor",
|
|
1810
|
+
p: "pipe",
|
|
1811
|
+
f: "filter",
|
|
1812
|
+
d: "dto",
|
|
1813
|
+
mw: "middleware",
|
|
1814
|
+
mi: "migration"
|
|
1815
|
+
};
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
// src/cli/templates/generators/index.ts
|
|
1819
|
+
function getGeneratorTemplate(type) {
|
|
1820
|
+
return generatorTemplates[type];
|
|
1821
|
+
}
|
|
1822
|
+
function getGeneratorTypes() {
|
|
1823
|
+
return Object.keys(generatorTemplates);
|
|
1824
|
+
}
|
|
1825
|
+
function getDefaultDirectory(type) {
|
|
1826
|
+
switch (type) {
|
|
1827
|
+
case "controller":
|
|
1828
|
+
case "service":
|
|
1829
|
+
case "module":
|
|
1830
|
+
case "dto":
|
|
1831
|
+
return "modules";
|
|
1832
|
+
case "guard":
|
|
1833
|
+
return "common/guards";
|
|
1834
|
+
case "interceptor":
|
|
1835
|
+
return "common/interceptors";
|
|
1836
|
+
case "pipe":
|
|
1837
|
+
return "common/pipes";
|
|
1838
|
+
case "filter":
|
|
1839
|
+
return "common/filters";
|
|
1840
|
+
case "middleware":
|
|
1841
|
+
return "common/middleware";
|
|
1842
|
+
case "migration":
|
|
1843
|
+
return "database/migrations";
|
|
1844
|
+
default:
|
|
1845
|
+
return "";
|
|
308
1846
|
}
|
|
309
|
-
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
310
1847
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
return `${ms}ms`;
|
|
314
|
-
if (ms < 60000)
|
|
315
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
316
|
-
return `${(ms / 60000).toFixed(1)}m`;
|
|
1848
|
+
function getFileExtension(type) {
|
|
1849
|
+
return type === "dto" ? ".dto.ts" : ".ts";
|
|
317
1850
|
}
|
|
1851
|
+
function getControllerTemplate(config) {
|
|
1852
|
+
return `import { Controller, Get, Post, Put, Delete{{#if path}} } from '@buenojs/bueno'{{/if}}{{#if service}}, { {{pascalCase service}}Service } from './{{kebabCase service}}.service'{{/if}};
|
|
1853
|
+
import type { Context } from '@buenojs/bueno';
|
|
318
1854
|
|
|
319
|
-
|
|
320
|
-
class
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
if (definition.alias) {
|
|
329
|
-
this.aliases.set(definition.alias, definition.name);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
get(name) {
|
|
333
|
-
const commandName = this.aliases.get(name) ?? name;
|
|
334
|
-
return this.commands.get(commandName);
|
|
335
|
-
}
|
|
336
|
-
has(name) {
|
|
337
|
-
const commandName = this.aliases.get(name) ?? name;
|
|
338
|
-
return this.commands.has(commandName);
|
|
339
|
-
}
|
|
340
|
-
getAll() {
|
|
341
|
-
return Array.from(this.commands.values()).map((c) => c.definition);
|
|
1855
|
+
@Controller('{{path}}')
|
|
1856
|
+
export class {{pascalCase name}}Controller {
|
|
1857
|
+
{{#if service}}
|
|
1858
|
+
constructor(private readonly {{camelCase service}}Service: {{pascalCase service}}Service) {}
|
|
1859
|
+
{{/if}}
|
|
1860
|
+
|
|
1861
|
+
@Get()
|
|
1862
|
+
async findAll(ctx: Context) {
|
|
1863
|
+
return { message: '{{pascalCase name}} controller' };
|
|
342
1864
|
}
|
|
343
|
-
|
|
344
|
-
|
|
1865
|
+
|
|
1866
|
+
@Get(':id')
|
|
1867
|
+
async findOne(ctx: Context) {
|
|
1868
|
+
const id = ctx.params.id;
|
|
1869
|
+
return { id, message: '{{pascalCase name}} item' };
|
|
345
1870
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
await command.handler(args);
|
|
1871
|
+
|
|
1872
|
+
@Post()
|
|
1873
|
+
async create(ctx: Context) {
|
|
1874
|
+
const body = await ctx.body();
|
|
1875
|
+
return { message: 'Created', data: body };
|
|
352
1876
|
}
|
|
353
|
-
}
|
|
354
|
-
var registry = new CommandRegistry;
|
|
355
|
-
function defineCommand(definition, handler) {
|
|
356
|
-
registry.register(definition, handler);
|
|
357
|
-
}
|
|
358
1877
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
function createRL() {
|
|
365
|
-
return readline.createInterface({
|
|
366
|
-
input: process.stdin,
|
|
367
|
-
output: process.stdout
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
async function prompt(message, options = {}) {
|
|
371
|
-
const defaultValue = options.default;
|
|
372
|
-
const promptText = defaultValue ? `${colors.cyan("?")} ${message} ${colors.dim(`(${defaultValue})`)}: ` : `${colors.cyan("?")} ${message}: `;
|
|
373
|
-
if (!isInteractive()) {
|
|
374
|
-
return defaultValue ?? "";
|
|
1878
|
+
@Put(':id')
|
|
1879
|
+
async update(ctx: Context) {
|
|
1880
|
+
const id = ctx.params.id;
|
|
1881
|
+
const body = await ctx.body();
|
|
1882
|
+
return { id, message: 'Updated', data: body };
|
|
375
1883
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (options.validate) {
|
|
382
|
-
const result = options.validate(value);
|
|
383
|
-
if (result !== true) {
|
|
384
|
-
const errorMsg = typeof result === "string" ? result : "Invalid value";
|
|
385
|
-
process.stdout.write(`${colors.red("\u2717")} ${errorMsg}
|
|
386
|
-
`);
|
|
387
|
-
prompt(message, options).then(resolve);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
resolve(value);
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
async function confirm(message, options = {}) {
|
|
396
|
-
const defaultValue = options.default ?? false;
|
|
397
|
-
const hint = defaultValue ? "Y/n" : "y/N";
|
|
398
|
-
if (!isInteractive()) {
|
|
399
|
-
return defaultValue;
|
|
1884
|
+
|
|
1885
|
+
@Delete(':id')
|
|
1886
|
+
async remove(ctx: Context) {
|
|
1887
|
+
const id = ctx.params.id;
|
|
1888
|
+
return { id, message: 'Deleted' };
|
|
400
1889
|
}
|
|
401
|
-
const answer = await prompt(`${message} ${colors.dim(`(${hint})`)}`, {
|
|
402
|
-
default: defaultValue ? "y" : "n",
|
|
403
|
-
validate: (value) => {
|
|
404
|
-
if (!value)
|
|
405
|
-
return true;
|
|
406
|
-
return ["y", "yes", "n", "no"].includes(value.toLowerCase()) || "Please enter y or n";
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
return ["y", "yes"].includes(answer.toLowerCase());
|
|
410
1890
|
}
|
|
411
|
-
|
|
412
|
-
if (!isInteractive()) {
|
|
413
|
-
return options.default ?? choices[0]?.value;
|
|
414
|
-
}
|
|
415
|
-
const pageSize = options.pageSize ?? 10;
|
|
416
|
-
let selectedIndex = choices.findIndex((c) => c.value === options.default && !c.disabled);
|
|
417
|
-
if (selectedIndex === -1) {
|
|
418
|
-
selectedIndex = choices.findIndex((c) => !c.disabled);
|
|
419
|
-
}
|
|
420
|
-
return new Promise((resolve) => {
|
|
421
|
-
process.stdout.write("\x1B[?25l");
|
|
422
|
-
const render = () => {
|
|
423
|
-
const lines = Math.min(choices.length, pageSize);
|
|
424
|
-
process.stdout.write(`\x1B[${lines + 1}A\x1B[0J`);
|
|
425
|
-
process.stdout.write(`${colors.cyan("?")} ${message}
|
|
426
|
-
`);
|
|
427
|
-
const start = Math.max(0, selectedIndex - pageSize + 1);
|
|
428
|
-
const end = Math.min(choices.length, start + pageSize);
|
|
429
|
-
for (let i = start;i < end; i++) {
|
|
430
|
-
const choice = choices[i];
|
|
431
|
-
if (!choice)
|
|
432
|
-
continue;
|
|
433
|
-
const isSelected = i === selectedIndex;
|
|
434
|
-
const prefix = isSelected ? `${colors.cyan("\u276F")} ` : " ";
|
|
435
|
-
const name = choice.name ?? choice.value;
|
|
436
|
-
const text = choice.disabled ? colors.dim(`${name} (disabled)`) : isSelected ? colors.cyan(name) : name;
|
|
437
|
-
process.stdout.write(`${prefix}${text}
|
|
438
|
-
`);
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
process.stdout.write(`${colors.cyan("?")} ${message}
|
|
442
|
-
`);
|
|
443
|
-
render();
|
|
444
|
-
const stdin = process.stdin;
|
|
445
|
-
stdin.setRawMode(true);
|
|
446
|
-
stdin.resume();
|
|
447
|
-
stdin.setEncoding("utf8");
|
|
448
|
-
const cleanup = () => {
|
|
449
|
-
stdin.setRawMode(false);
|
|
450
|
-
stdin.pause();
|
|
451
|
-
stdin.removeListener("data", handler);
|
|
452
|
-
process.stdout.write("\x1B[?25h");
|
|
453
|
-
};
|
|
454
|
-
const handler = (key) => {
|
|
455
|
-
if (key === "\x1B[A" || key === "k") {
|
|
456
|
-
do {
|
|
457
|
-
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
458
|
-
} while (choices[selectedIndex]?.disabled);
|
|
459
|
-
render();
|
|
460
|
-
} else if (key === "\x1B[B" || key === "j") {
|
|
461
|
-
do {
|
|
462
|
-
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
463
|
-
} while (choices[selectedIndex]?.disabled);
|
|
464
|
-
render();
|
|
465
|
-
} else if (key === "\r" || key === `
|
|
466
|
-
`) {
|
|
467
|
-
cleanup();
|
|
468
|
-
const selected = choices[selectedIndex];
|
|
469
|
-
if (selected) {
|
|
470
|
-
process.stdout.write(`\x1B[${Math.min(choices.length, pageSize) + 1}A\x1B[0J`);
|
|
471
|
-
process.stdout.write(`${colors.cyan("?")} ${message} ${colors.cyan(selected.name ?? selected.value)}
|
|
472
|
-
`);
|
|
473
|
-
resolve(selected.value);
|
|
474
|
-
}
|
|
475
|
-
} else if (key === "\x1B" || key === "\x03") {
|
|
476
|
-
cleanup();
|
|
477
|
-
process.stdout.write(`
|
|
478
|
-
`);
|
|
479
|
-
process.exit(130);
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
stdin.on("data", handler);
|
|
483
|
-
});
|
|
1891
|
+
`;
|
|
484
1892
|
}
|
|
1893
|
+
function getServiceTemplate() {
|
|
1894
|
+
return `import { Injectable } from '@buenojs/bueno';
|
|
485
1895
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
text;
|
|
492
|
-
color;
|
|
493
|
-
frameIndex = 0;
|
|
494
|
-
interval = null;
|
|
495
|
-
isSpinning = false;
|
|
496
|
-
stream = process.stdout;
|
|
497
|
-
constructor(options = {}) {
|
|
498
|
-
this.text = options.text ?? "";
|
|
499
|
-
this.color = options.color ?? "cyan";
|
|
1896
|
+
@Injectable()
|
|
1897
|
+
export class {{pascalCase name}}Service {
|
|
1898
|
+
async findAll() {
|
|
1899
|
+
// TODO: Implement findAll
|
|
1900
|
+
return [];
|
|
500
1901
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return this;
|
|
506
|
-
this.isSpinning = true;
|
|
507
|
-
this.frameIndex = 0;
|
|
508
|
-
this.stream.write("\x1B[?25l");
|
|
509
|
-
this.interval = setInterval(() => {
|
|
510
|
-
this.render();
|
|
511
|
-
}, SPINNER_INTERVAL);
|
|
512
|
-
return this;
|
|
1902
|
+
|
|
1903
|
+
async findOne(id: string) {
|
|
1904
|
+
// TODO: Implement findOne
|
|
1905
|
+
return { id };
|
|
513
1906
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
return this;
|
|
1907
|
+
|
|
1908
|
+
async create(data: unknown) {
|
|
1909
|
+
// TODO: Implement create
|
|
1910
|
+
return data;
|
|
520
1911
|
}
|
|
521
|
-
|
|
522
|
-
|
|
1912
|
+
|
|
1913
|
+
async update(id: string, data: unknown) {
|
|
1914
|
+
// TODO: Implement update
|
|
1915
|
+
return { id, ...data };
|
|
523
1916
|
}
|
|
524
|
-
|
|
525
|
-
|
|
1917
|
+
|
|
1918
|
+
async remove(id: string) {
|
|
1919
|
+
// TODO: Implement remove
|
|
1920
|
+
return { id };
|
|
526
1921
|
}
|
|
527
|
-
|
|
528
|
-
|
|
1922
|
+
}
|
|
1923
|
+
`;
|
|
1924
|
+
}
|
|
1925
|
+
function getModuleTemplate(config) {
|
|
1926
|
+
return `import { Module } from '@buenojs/bueno';
|
|
1927
|
+
import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller';
|
|
1928
|
+
import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
|
|
1929
|
+
|
|
1930
|
+
@Module({
|
|
1931
|
+
controllers: [{{pascalCase name}}Controller],
|
|
1932
|
+
providers: [{{pascalCase name}}Service],
|
|
1933
|
+
exports: [{{pascalCase name}}Service],
|
|
1934
|
+
})
|
|
1935
|
+
export class {{pascalCase name}}Module {}
|
|
1936
|
+
`;
|
|
1937
|
+
}
|
|
1938
|
+
function getGuardTemplate(config) {
|
|
1939
|
+
return `import { Injectable, type CanActivate, type Context } from '@buenojs/bueno';
|
|
1940
|
+
|
|
1941
|
+
@Injectable()
|
|
1942
|
+
export class {{pascalCase name}}Guard implements CanActivate {
|
|
1943
|
+
async canActivate(ctx: Context): Promise<boolean> {
|
|
1944
|
+
// TODO: Implement guard logic
|
|
1945
|
+
// Return true to allow access, false to deny
|
|
1946
|
+
return true;
|
|
529
1947
|
}
|
|
530
|
-
|
|
531
|
-
|
|
1948
|
+
}
|
|
1949
|
+
`;
|
|
1950
|
+
}
|
|
1951
|
+
function getInterceptorTemplate(config) {
|
|
1952
|
+
return `import { Injectable, type NestInterceptor, type CallHandler, type Context } from '@buenojs/bueno';
|
|
1953
|
+
import type { Observable } from 'rxjs';
|
|
1954
|
+
|
|
1955
|
+
@Injectable()
|
|
1956
|
+
export class {{pascalCase name}}Interceptor implements NestInterceptor {
|
|
1957
|
+
async intercept(ctx: Context, next: CallHandler): Promise<Observable<unknown>> {
|
|
1958
|
+
// Before handler execution
|
|
1959
|
+
console.log('{{pascalCase name}}Interceptor - Before');
|
|
1960
|
+
|
|
1961
|
+
// Call the handler
|
|
1962
|
+
const result = await next.handle();
|
|
1963
|
+
|
|
1964
|
+
// After handler execution
|
|
1965
|
+
console.log('{{pascalCase name}}Interceptor - After');
|
|
1966
|
+
|
|
1967
|
+
return result;
|
|
532
1968
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
`);
|
|
546
|
-
} else {
|
|
547
|
-
this.stream.write(`${finalText}
|
|
548
|
-
`);
|
|
549
|
-
}
|
|
550
|
-
this.stream.write("\x1B[?25h");
|
|
551
|
-
return this;
|
|
1969
|
+
}
|
|
1970
|
+
`;
|
|
1971
|
+
}
|
|
1972
|
+
function getPipeTemplate(config) {
|
|
1973
|
+
return `import { Injectable, type PipeTransform, type Context } from '@buenojs/bueno';
|
|
1974
|
+
|
|
1975
|
+
@Injectable()
|
|
1976
|
+
export class {{pascalCase name}}Pipe implements PipeTransform {
|
|
1977
|
+
async transform(value: unknown, ctx: Context): Promise<unknown> {
|
|
1978
|
+
// TODO: Implement transformation/validation logic
|
|
1979
|
+
// Throw an error to reject the value
|
|
1980
|
+
return value;
|
|
552
1981
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1982
|
+
}
|
|
1983
|
+
`;
|
|
1984
|
+
}
|
|
1985
|
+
function getFilterTemplate(config) {
|
|
1986
|
+
return `import { Injectable, type ExceptionFilter, type Context } from '@buenojs/bueno';
|
|
1987
|
+
import type { Response } from '@buenojs/bueno';
|
|
1988
|
+
|
|
1989
|
+
@Injectable()
|
|
1990
|
+
export class {{pascalCase name}}Filter implements ExceptionFilter {
|
|
1991
|
+
async catch(exception: Error, ctx: Context): Promise<Response> {
|
|
1992
|
+
// TODO: Implement exception handling
|
|
1993
|
+
console.error('{{pascalCase name}}Filter caught:', exception);
|
|
1994
|
+
|
|
1995
|
+
return new Response(
|
|
1996
|
+
JSON.stringify({
|
|
1997
|
+
statusCode: 500,
|
|
1998
|
+
message: 'Internal Server Error',
|
|
1999
|
+
error: exception.message,
|
|
2000
|
+
}),
|
|
2001
|
+
{
|
|
2002
|
+
status: 500,
|
|
2003
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2004
|
+
}
|
|
2005
|
+
);
|
|
558
2006
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
2007
|
+
}
|
|
2008
|
+
`;
|
|
2009
|
+
}
|
|
2010
|
+
function getDtoTemplate(config) {
|
|
2011
|
+
return `/**
|
|
2012
|
+
* {{pascalCase name}} DTO
|
|
2013
|
+
*/
|
|
2014
|
+
export interface {{pascalCase name}}Dto {
|
|
2015
|
+
// TODO: Define properties
|
|
2016
|
+
id?: string;
|
|
2017
|
+
createdAt?: Date;
|
|
2018
|
+
updatedAt?: Date;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* Create {{pascalCase name}} DTO
|
|
2023
|
+
*/
|
|
2024
|
+
export interface Create{{pascalCase name}}Dto {
|
|
2025
|
+
// TODO: Define required properties for creation
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* Update {{pascalCase name}} DTO
|
|
2030
|
+
*/
|
|
2031
|
+
export interface Update{{pascalCase name}}Dto extends Partial<Create{{pascalCase name}}Dto> {
|
|
2032
|
+
// TODO: Define optional properties for update
|
|
2033
|
+
}
|
|
2034
|
+
`;
|
|
2035
|
+
}
|
|
2036
|
+
function getMiddlewareTemplate(config) {
|
|
2037
|
+
return `import type { Middleware, Context, Handler } from '@buenojs/bueno';
|
|
2038
|
+
|
|
2039
|
+
/**
|
|
2040
|
+
* {{pascalCase name}} Middleware
|
|
2041
|
+
*/
|
|
2042
|
+
export const {{camelCase name}}Middleware: Middleware = async (
|
|
2043
|
+
ctx: Context,
|
|
2044
|
+
next: Handler
|
|
2045
|
+
) => {
|
|
2046
|
+
// Before handler execution
|
|
2047
|
+
console.log('{{pascalCase name}}Middleware - Before');
|
|
2048
|
+
|
|
2049
|
+
// Call the next handler
|
|
2050
|
+
const result = await next();
|
|
2051
|
+
|
|
2052
|
+
// After handler execution
|
|
2053
|
+
console.log('{{pascalCase name}}Middleware - After');
|
|
2054
|
+
|
|
2055
|
+
return result;
|
|
2056
|
+
};
|
|
2057
|
+
`;
|
|
2058
|
+
}
|
|
2059
|
+
function getMigrationTemplate(config) {
|
|
2060
|
+
const migrationId = generateMigrationId();
|
|
2061
|
+
return `import { createMigration, type MigrationRunner } from '@buenojs/bueno';
|
|
2062
|
+
|
|
2063
|
+
export default createMigration('${migrationId}', '{{migrationName}}')
|
|
2064
|
+
.up(async (db: MigrationRunner) => {
|
|
2065
|
+
// TODO: Add migration logic
|
|
2066
|
+
// Example:
|
|
2067
|
+
// await db.createTable({
|
|
2068
|
+
// name: '{{tableName}}',
|
|
2069
|
+
// columns: [
|
|
2070
|
+
// { name: 'id', type: 'uuid', primary: true },
|
|
2071
|
+
// { name: 'created_at', type: 'timestamp', default: 'NOW()' },
|
|
2072
|
+
// ],
|
|
2073
|
+
// });
|
|
2074
|
+
})
|
|
2075
|
+
.down(async (db: MigrationRunner) => {
|
|
2076
|
+
// TODO: Add rollback logic
|
|
2077
|
+
// Example:
|
|
2078
|
+
// await db.dropTable('{{tableName}}');
|
|
2079
|
+
});
|
|
2080
|
+
`;
|
|
2081
|
+
}
|
|
2082
|
+
function generateMigrationId() {
|
|
2083
|
+
const now = new Date;
|
|
2084
|
+
const year = now.getFullYear();
|
|
2085
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
2086
|
+
const day = String(now.getDate()).padStart(2, "0");
|
|
2087
|
+
const hour = String(now.getHours()).padStart(2, "0");
|
|
2088
|
+
const minute = String(now.getMinutes()).padStart(2, "0");
|
|
2089
|
+
const second = String(now.getSeconds()).padStart(2, "0");
|
|
2090
|
+
return `${year}${month}${day}${hour}${minute}${second}`;
|
|
2091
|
+
}
|
|
2092
|
+
var generatorTemplates;
|
|
2093
|
+
var init_generators = __esm(() => {
|
|
2094
|
+
init_types();
|
|
2095
|
+
generatorTemplates = {
|
|
2096
|
+
controller: (config) => getControllerTemplate(config),
|
|
2097
|
+
service: () => getServiceTemplate(),
|
|
2098
|
+
module: (config) => getModuleTemplate(config),
|
|
2099
|
+
guard: (config) => getGuardTemplate(config),
|
|
2100
|
+
interceptor: (config) => getInterceptorTemplate(config),
|
|
2101
|
+
pipe: (config) => getPipeTemplate(config),
|
|
2102
|
+
filter: (config) => getFilterTemplate(config),
|
|
2103
|
+
dto: (config) => getDtoTemplate(config),
|
|
2104
|
+
middleware: (config) => getMiddlewareTemplate(config),
|
|
2105
|
+
migration: (config) => getMigrationTemplate(config)
|
|
2106
|
+
};
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
// src/cli/templates/index.ts
|
|
2110
|
+
var exports_templates = {};
|
|
2111
|
+
__export(exports_templates, {
|
|
2112
|
+
getTemplateOptions: () => getTemplateOptions,
|
|
2113
|
+
getRenderYamlTemplate: () => getRenderYamlTemplate,
|
|
2114
|
+
getRailwayTomlTemplate: () => getRailwayTomlTemplate,
|
|
2115
|
+
getProjectTemplate: () => getProjectTemplate,
|
|
2116
|
+
getGeneratorTypes: () => getGeneratorTypes,
|
|
2117
|
+
getGeneratorTemplate: () => getGeneratorTemplate,
|
|
2118
|
+
getFrontendTemplate: () => getFrontendTemplate,
|
|
2119
|
+
getFrontendOptions: () => getFrontendOptions,
|
|
2120
|
+
getFlyTomlTemplate: () => getFlyTomlTemplate,
|
|
2121
|
+
getFileExtension: () => getFileExtension,
|
|
2122
|
+
getDockerignoreTemplate: () => getDockerignoreTemplate,
|
|
2123
|
+
getDockerfileTemplate: () => getDockerfileTemplate,
|
|
2124
|
+
getDockerEnvTemplate: () => getDockerEnvTemplate,
|
|
2125
|
+
getDockerComposeTemplate: () => getDockerComposeTemplate,
|
|
2126
|
+
getDeployTemplate: () => getDeployTemplate,
|
|
2127
|
+
getDeployPlatformName: () => getDeployPlatformName,
|
|
2128
|
+
getDeployFilename: () => getDeployFilename,
|
|
2129
|
+
getDefaultDirectory: () => getDefaultDirectory,
|
|
2130
|
+
getDatabaseTemplate: () => getDatabaseTemplate,
|
|
2131
|
+
getDatabaseOptions: () => getDatabaseOptions,
|
|
2132
|
+
GENERATOR_ALIASES: () => GENERATOR_ALIASES
|
|
2133
|
+
});
|
|
2134
|
+
var init_templates = __esm(() => {
|
|
2135
|
+
init_project();
|
|
2136
|
+
init_database();
|
|
2137
|
+
init_frontend();
|
|
2138
|
+
init_generators();
|
|
2139
|
+
});
|
|
2140
|
+
|
|
2141
|
+
// src/cli/index.ts
|
|
2142
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2143
|
+
import { join as join3 } from "path";
|
|
2144
|
+
|
|
2145
|
+
// src/cli/core/args.ts
|
|
2146
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
2147
|
+
const result = {
|
|
2148
|
+
command: "",
|
|
2149
|
+
positionals: [],
|
|
2150
|
+
options: {},
|
|
2151
|
+
flags: new Set
|
|
2152
|
+
};
|
|
2153
|
+
for (let i = 0;i < argv.length; i++) {
|
|
2154
|
+
const arg = argv[i];
|
|
2155
|
+
if (!arg)
|
|
2156
|
+
continue;
|
|
2157
|
+
if (arg.startsWith("--")) {
|
|
2158
|
+
const eqIndex = arg.indexOf("=");
|
|
2159
|
+
if (eqIndex !== -1) {
|
|
2160
|
+
const name = arg.slice(2, eqIndex);
|
|
2161
|
+
const value = arg.slice(eqIndex + 1);
|
|
2162
|
+
result.options[name] = value;
|
|
2163
|
+
} else {
|
|
2164
|
+
const name = arg.slice(2);
|
|
2165
|
+
const nextArg = argv[i + 1];
|
|
2166
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
2167
|
+
result.options[name] = true;
|
|
2168
|
+
result.flags.add(name);
|
|
2169
|
+
} else {
|
|
2170
|
+
result.options[name] = nextArg;
|
|
2171
|
+
i++;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
} else if (arg.startsWith("-") && arg.length > 1) {
|
|
2175
|
+
const chars = arg.slice(1);
|
|
2176
|
+
if (chars.length > 1) {
|
|
2177
|
+
for (const char of chars) {
|
|
2178
|
+
result.options[char] = true;
|
|
2179
|
+
result.flags.add(char);
|
|
2180
|
+
}
|
|
2181
|
+
} else {
|
|
2182
|
+
const name = chars;
|
|
2183
|
+
const nextArg = argv[i + 1];
|
|
2184
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
2185
|
+
result.options[name] = true;
|
|
2186
|
+
result.flags.add(name);
|
|
2187
|
+
} else {
|
|
2188
|
+
result.options[name] = nextArg;
|
|
2189
|
+
i++;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
} else {
|
|
2193
|
+
if (!result.command) {
|
|
2194
|
+
result.command = arg;
|
|
2195
|
+
} else {
|
|
2196
|
+
result.positionals.push(arg);
|
|
2197
|
+
}
|
|
565
2198
|
}
|
|
566
|
-
const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
|
|
567
|
-
const coloredFrame = colors[this.color](frame);
|
|
568
|
-
this.stream.write(`\r${coloredFrame} ${this.text}`);
|
|
569
|
-
this.frameIndex++;
|
|
570
2199
|
}
|
|
2200
|
+
return result;
|
|
571
2201
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
class ProgressBar {
|
|
577
|
-
total;
|
|
578
|
-
width;
|
|
579
|
-
text;
|
|
580
|
-
completeChar;
|
|
581
|
-
incompleteChar;
|
|
582
|
-
current = 0;
|
|
583
|
-
stream = process.stdout;
|
|
584
|
-
constructor(options) {
|
|
585
|
-
this.total = options.total;
|
|
586
|
-
this.width = options.width ?? 40;
|
|
587
|
-
this.text = options.text ?? "";
|
|
588
|
-
this.completeChar = options.completeChar ?? "\u2588";
|
|
589
|
-
this.incompleteChar = options.incompleteChar ?? "\u2591";
|
|
590
|
-
}
|
|
591
|
-
start() {
|
|
592
|
-
this.current = 0;
|
|
593
|
-
this.render();
|
|
594
|
-
return this;
|
|
595
|
-
}
|
|
596
|
-
update(current) {
|
|
597
|
-
this.current = Math.min(current, this.total);
|
|
598
|
-
this.render();
|
|
599
|
-
return this;
|
|
600
|
-
}
|
|
601
|
-
increment(amount = 1) {
|
|
602
|
-
return this.update(this.current + amount);
|
|
603
|
-
}
|
|
604
|
-
complete() {
|
|
605
|
-
this.current = this.total;
|
|
606
|
-
this.render();
|
|
607
|
-
this.stream.write(`
|
|
608
|
-
`);
|
|
609
|
-
return this;
|
|
610
|
-
}
|
|
611
|
-
render() {
|
|
612
|
-
const percent = this.current / this.total;
|
|
613
|
-
const completeWidth = Math.round(this.width * percent);
|
|
614
|
-
const incompleteWidth = this.width - completeWidth;
|
|
615
|
-
const complete = this.completeChar.repeat(completeWidth);
|
|
616
|
-
const incomplete = this.incompleteChar.repeat(incompleteWidth);
|
|
617
|
-
const bar = colors.green(complete) + colors.dim(incomplete);
|
|
618
|
-
const percentText = `${Math.round(percent * 100)}%`.padStart(4);
|
|
619
|
-
const line = `\r${this.text} [${bar}] ${percentText} ${this.current}/${this.total}`;
|
|
620
|
-
this.stream.write(`\r\x1B[K${line}`);
|
|
2202
|
+
function getOption(parsed, name, definition) {
|
|
2203
|
+
const value = parsed.options[name] ?? parsed.options[definition.alias ?? ""];
|
|
2204
|
+
if (value === undefined) {
|
|
2205
|
+
return definition.default;
|
|
621
2206
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
for (const task of tasks) {
|
|
625
|
-
const s = spinner(task.text);
|
|
626
|
-
try {
|
|
627
|
-
await task.task();
|
|
628
|
-
s.success();
|
|
629
|
-
} catch (error) {
|
|
630
|
-
s.error();
|
|
631
|
-
throw error;
|
|
632
|
-
}
|
|
2207
|
+
if (definition.type === "boolean") {
|
|
2208
|
+
return value === true || value === "true";
|
|
633
2209
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
// src/cli/utils/fs.ts
|
|
637
|
-
import * as fs from "fs";
|
|
638
|
-
import * as path from "path";
|
|
639
|
-
async function fileExists(filePath) {
|
|
640
|
-
try {
|
|
641
|
-
return await Bun.file(filePath).exists();
|
|
642
|
-
} catch {
|
|
643
|
-
return false;
|
|
2210
|
+
if (definition.type === "number") {
|
|
2211
|
+
return typeof value === "number" ? value : typeof value === "string" ? parseInt(value, 10) : NaN;
|
|
644
2212
|
}
|
|
2213
|
+
return value;
|
|
645
2214
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
async function readFile(filePath) {
|
|
650
|
-
return await Bun.file(filePath).text();
|
|
651
|
-
}
|
|
652
|
-
async function writeFile(filePath, content) {
|
|
653
|
-
const dir = path.dirname(filePath);
|
|
654
|
-
await createDirectory(dir);
|
|
655
|
-
await Bun.write(filePath, content);
|
|
656
|
-
}
|
|
657
|
-
async function deleteDirectory(dirPath) {
|
|
658
|
-
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
2215
|
+
function hasFlag(parsed, name, alias) {
|
|
2216
|
+
return parsed.flags.has(name) || (alias ? parsed.flags.has(alias) : false);
|
|
659
2217
|
}
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
2218
|
+
function getOptionValues(parsed, name, alias) {
|
|
2219
|
+
const values = [];
|
|
2220
|
+
const argv = process.argv.slice(2);
|
|
2221
|
+
for (let i = 0;i < argv.length; i++) {
|
|
2222
|
+
const arg = argv[i];
|
|
2223
|
+
if (!arg)
|
|
2224
|
+
continue;
|
|
2225
|
+
if (arg === `--${name}`) {
|
|
2226
|
+
const nextArg = argv[i + 1];
|
|
2227
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
2228
|
+
values.push(nextArg);
|
|
2229
|
+
i++;
|
|
2230
|
+
}
|
|
2231
|
+
} else if (arg.startsWith(`--${name}=`)) {
|
|
2232
|
+
const value = arg.slice(name.length + 3);
|
|
2233
|
+
values.push(value);
|
|
2234
|
+
} else if (alias && arg === `-${alias}`) {
|
|
2235
|
+
const nextArg = argv[i + 1];
|
|
2236
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
2237
|
+
values.push(nextArg);
|
|
2238
|
+
i++;
|
|
672
2239
|
}
|
|
673
2240
|
}
|
|
674
2241
|
}
|
|
675
|
-
|
|
676
|
-
return files;
|
|
2242
|
+
return values;
|
|
677
2243
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
2244
|
+
function generateHelpText(command, cliName = "bueno") {
|
|
2245
|
+
const lines = [];
|
|
2246
|
+
lines.push(`
|
|
2247
|
+
${command.description}
|
|
2248
|
+
`);
|
|
2249
|
+
lines.push("Usage:");
|
|
2250
|
+
let usage = ` ${cliName} ${command.name}`;
|
|
2251
|
+
if (command.positionals) {
|
|
2252
|
+
for (const pos of command.positionals) {
|
|
2253
|
+
usage += pos.required ? ` <${pos.name}>` : ` [${pos.name}]`;
|
|
685
2254
|
}
|
|
686
|
-
currentDir = path.dirname(currentDir);
|
|
687
|
-
}
|
|
688
|
-
return null;
|
|
689
|
-
}
|
|
690
|
-
async function getProjectRoot(startDir = process.cwd()) {
|
|
691
|
-
const packageJsonPath = await findFileUp(startDir, "package.json");
|
|
692
|
-
if (packageJsonPath) {
|
|
693
|
-
return path.dirname(packageJsonPath);
|
|
694
2255
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
return true;
|
|
704
|
-
const packageJsonPath = path.join(root, "package.json");
|
|
705
|
-
if (await fileExists(packageJsonPath)) {
|
|
706
|
-
const content = await readFile(packageJsonPath);
|
|
707
|
-
try {
|
|
708
|
-
const pkg = JSON.parse(content);
|
|
709
|
-
return !!(pkg.dependencies?.bueno || pkg.devDependencies?.bueno);
|
|
710
|
-
} catch {
|
|
711
|
-
return false;
|
|
2256
|
+
usage += " [options]";
|
|
2257
|
+
lines.push(usage + `
|
|
2258
|
+
`);
|
|
2259
|
+
if (command.positionals && command.positionals.length > 0) {
|
|
2260
|
+
lines.push("Arguments:");
|
|
2261
|
+
for (const pos of command.positionals) {
|
|
2262
|
+
const required = pos.required ? " (required)" : "";
|
|
2263
|
+
lines.push(` ${pos.name.padEnd(20)} ${pos.description}${required}`);
|
|
712
2264
|
}
|
|
2265
|
+
lines.push("");
|
|
713
2266
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
let result = template;
|
|
721
|
-
result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
|
|
722
|
-
const value = data[key];
|
|
723
|
-
return value ? content : "";
|
|
724
|
-
});
|
|
725
|
-
result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
|
|
726
|
-
const items = data[key];
|
|
727
|
-
if (!Array.isArray(items))
|
|
728
|
-
return "";
|
|
729
|
-
return items.map((item) => {
|
|
730
|
-
let itemContent = content;
|
|
731
|
-
if (typeof item === "object" && item !== null) {
|
|
732
|
-
for (const [k, v] of Object.entries(item)) {
|
|
733
|
-
itemContent = itemContent.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return itemContent;
|
|
737
|
-
}).join("");
|
|
738
|
-
});
|
|
739
|
-
const helpers = {
|
|
740
|
-
camelCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase()),
|
|
741
|
-
pascalCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase()),
|
|
742
|
-
kebabCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase(),
|
|
743
|
-
snakeCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase(),
|
|
744
|
-
upperCase: (v) => v.toUpperCase(),
|
|
745
|
-
lowerCase: (v) => v.toLowerCase(),
|
|
746
|
-
capitalize: (v) => v.charAt(0).toUpperCase() + v.slice(1),
|
|
747
|
-
pluralize: (v) => {
|
|
748
|
-
if (v.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((e) => v.endsWith(e))) {
|
|
749
|
-
return v.slice(0, -1) + "ies";
|
|
2267
|
+
if (command.options && command.options.length > 0) {
|
|
2268
|
+
lines.push("Options:");
|
|
2269
|
+
for (const opt of command.options) {
|
|
2270
|
+
let flag = `--${opt.name}`;
|
|
2271
|
+
if (opt.alias) {
|
|
2272
|
+
flag = `-${opt.alias}, ${flag}`;
|
|
750
2273
|
}
|
|
751
|
-
|
|
752
|
-
|
|
2274
|
+
let defaultValue = "";
|
|
2275
|
+
if (opt.default !== undefined) {
|
|
2276
|
+
defaultValue = ` (default: ${opt.default})`;
|
|
753
2277
|
}
|
|
754
|
-
|
|
2278
|
+
lines.push(` ${flag.padEnd(20)} ${opt.description}${defaultValue}`);
|
|
755
2279
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return String(value);
|
|
765
|
-
});
|
|
2280
|
+
lines.push("");
|
|
2281
|
+
}
|
|
2282
|
+
if (command.examples && command.examples.length > 0) {
|
|
2283
|
+
lines.push("Examples:");
|
|
2284
|
+
for (const example of command.examples) {
|
|
2285
|
+
lines.push(` ${example}`);
|
|
2286
|
+
}
|
|
2287
|
+
lines.push("");
|
|
766
2288
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
2289
|
+
return lines.join(`
|
|
2290
|
+
`);
|
|
2291
|
+
}
|
|
2292
|
+
function generateGlobalHelpText(commands, cliName = "bueno") {
|
|
2293
|
+
const lines = [];
|
|
2294
|
+
lines.push(`
|
|
2295
|
+
${cliName} - A Bun-Native Full-Stack Framework CLI
|
|
2296
|
+
`);
|
|
2297
|
+
lines.push("Usage:");
|
|
2298
|
+
lines.push(` ${cliName} <command> [options]
|
|
2299
|
+
`);
|
|
2300
|
+
lines.push("Commands:");
|
|
2301
|
+
for (const cmd of commands) {
|
|
2302
|
+
const name = cmd.alias ? `${cmd.name} (${cmd.alias})` : cmd.name;
|
|
2303
|
+
lines.push(` ${name.padEnd(20)} ${cmd.description}`);
|
|
770
2304
|
}
|
|
771
|
-
|
|
2305
|
+
lines.push("");
|
|
2306
|
+
lines.push("Global Options:");
|
|
2307
|
+
lines.push(" --help, -h Show help for command");
|
|
2308
|
+
lines.push(" --version, -v Show CLI version");
|
|
2309
|
+
lines.push(" --verbose Enable verbose output");
|
|
2310
|
+
lines.push(" --quiet Suppress non-essential output");
|
|
2311
|
+
lines.push(" --no-color Disable colored output");
|
|
2312
|
+
lines.push("");
|
|
2313
|
+
lines.push(`Run '${cliName} <command> --help' for more information about a command.
|
|
772
2314
|
`);
|
|
773
|
-
|
|
774
|
-
|
|
2315
|
+
return lines.join(`
|
|
775
2316
|
`);
|
|
776
|
-
return result.trim();
|
|
777
2317
|
}
|
|
778
2318
|
|
|
779
|
-
// src/cli/
|
|
780
|
-
|
|
781
|
-
|
|
2319
|
+
// src/cli/core/console.ts
|
|
2320
|
+
var COLORS = {
|
|
2321
|
+
reset: "\x1B[0m",
|
|
2322
|
+
bold: "\x1B[1m",
|
|
2323
|
+
dim: "\x1B[2m",
|
|
2324
|
+
italic: "\x1B[3m",
|
|
2325
|
+
underline: "\x1B[4m",
|
|
2326
|
+
black: "\x1B[30m",
|
|
2327
|
+
red: "\x1B[31m",
|
|
2328
|
+
green: "\x1B[32m",
|
|
2329
|
+
yellow: "\x1B[33m",
|
|
2330
|
+
blue: "\x1B[34m",
|
|
2331
|
+
magenta: "\x1B[35m",
|
|
2332
|
+
cyan: "\x1B[36m",
|
|
2333
|
+
white: "\x1B[37m",
|
|
2334
|
+
brightRed: "\x1B[91m",
|
|
2335
|
+
brightGreen: "\x1B[92m",
|
|
2336
|
+
brightYellow: "\x1B[93m",
|
|
2337
|
+
brightBlue: "\x1B[94m",
|
|
2338
|
+
brightMagenta: "\x1B[95m",
|
|
2339
|
+
brightCyan: "\x1B[96m",
|
|
2340
|
+
brightWhite: "\x1B[97m",
|
|
2341
|
+
bgBlack: "\x1B[40m",
|
|
2342
|
+
bgRed: "\x1B[41m",
|
|
2343
|
+
bgGreen: "\x1B[42m",
|
|
2344
|
+
bgYellow: "\x1B[43m",
|
|
2345
|
+
bgBlue: "\x1B[44m",
|
|
2346
|
+
bgMagenta: "\x1B[45m",
|
|
2347
|
+
bgCyan: "\x1B[46m",
|
|
2348
|
+
bgWhite: "\x1B[47m"
|
|
2349
|
+
};
|
|
2350
|
+
var colorEnabled = !process.env.NO_COLOR && process.env.BUENO_NO_COLOR !== "true" && process.stdout.isTTY;
|
|
2351
|
+
function setColorEnabled(enabled) {
|
|
2352
|
+
colorEnabled = enabled;
|
|
782
2353
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
function getDockerfileTemplate(projectName, database) {
|
|
786
|
-
return `# ${projectName} - Production Dockerfile
|
|
787
|
-
# Multi-stage build for optimized production image
|
|
788
|
-
|
|
789
|
-
# Stage 1: Install dependencies
|
|
790
|
-
FROM oven/bun:1 AS deps
|
|
791
|
-
|
|
792
|
-
WORKDIR /app
|
|
793
|
-
|
|
794
|
-
# Copy package files first for better layer caching
|
|
795
|
-
COPY package.json bun.lock* ./
|
|
796
|
-
|
|
797
|
-
# Install dependencies
|
|
798
|
-
RUN bun install --frozen-lockfile --production
|
|
799
|
-
|
|
800
|
-
# Stage 2: Build the application
|
|
801
|
-
FROM oven/bun:1 AS builder
|
|
802
|
-
|
|
803
|
-
WORKDIR /app
|
|
804
|
-
|
|
805
|
-
# Copy package files
|
|
806
|
-
COPY package.json bun.lock* ./
|
|
807
|
-
|
|
808
|
-
# Install all dependencies (including devDependencies for build)
|
|
809
|
-
RUN bun install --frozen-lockfile
|
|
810
|
-
|
|
811
|
-
# Copy source code
|
|
812
|
-
COPY . .
|
|
813
|
-
|
|
814
|
-
# Build the application
|
|
815
|
-
RUN bun run build
|
|
816
|
-
|
|
817
|
-
# Stage 3: Production image
|
|
818
|
-
FROM oven/bun:1 AS runner
|
|
819
|
-
|
|
820
|
-
WORKDIR /app
|
|
821
|
-
|
|
822
|
-
# Set production environment
|
|
823
|
-
ENV NODE_ENV=production
|
|
824
|
-
ENV BUN_ENV=production
|
|
825
|
-
|
|
826
|
-
# Create non-root user for security
|
|
827
|
-
RUN addgroup --system --gid 1001 bunjs \\
|
|
828
|
-
&& adduser --system --uid 1001 --ingroup bunjs bunuser
|
|
829
|
-
|
|
830
|
-
# Copy built application from builder
|
|
831
|
-
COPY --from=builder /app/dist ./dist
|
|
832
|
-
COPY --from=builder /app/node_modules ./node_modules
|
|
833
|
-
COPY --from=builder /app/package.json ./
|
|
834
|
-
|
|
835
|
-
# Copy config files if they exist
|
|
836
|
-
COPY --from=builder /app/bueno.config.ts* ./
|
|
837
|
-
|
|
838
|
-
# Set proper ownership
|
|
839
|
-
RUN chown -R bunuser:bunjs /app
|
|
840
|
-
|
|
841
|
-
# Switch to non-root user
|
|
842
|
-
USER bunuser
|
|
843
|
-
|
|
844
|
-
# Expose the application port
|
|
845
|
-
EXPOSE 3000
|
|
846
|
-
|
|
847
|
-
# Health check
|
|
848
|
-
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\
|
|
849
|
-
CMD curl -f http://localhost:3000/health || exit 1
|
|
850
|
-
|
|
851
|
-
# Start the application
|
|
852
|
-
CMD ["bun", "run", "dist/main.js"]
|
|
853
|
-
`;
|
|
2354
|
+
function isColorEnabled() {
|
|
2355
|
+
return colorEnabled;
|
|
854
2356
|
}
|
|
855
|
-
function
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
2357
|
+
function colorize(text, color) {
|
|
2358
|
+
if (!colorEnabled)
|
|
2359
|
+
return text;
|
|
2360
|
+
return `${COLORS[color]}${text}${COLORS.reset}`;
|
|
2361
|
+
}
|
|
2362
|
+
var colors = {
|
|
2363
|
+
red: (text) => colorize(text, "red"),
|
|
2364
|
+
green: (text) => colorize(text, "green"),
|
|
2365
|
+
yellow: (text) => colorize(text, "yellow"),
|
|
2366
|
+
blue: (text) => colorize(text, "blue"),
|
|
2367
|
+
magenta: (text) => colorize(text, "magenta"),
|
|
2368
|
+
cyan: (text) => colorize(text, "cyan"),
|
|
2369
|
+
white: (text) => colorize(text, "white"),
|
|
2370
|
+
brightRed: (text) => colorize(text, "brightRed"),
|
|
2371
|
+
brightGreen: (text) => colorize(text, "brightGreen"),
|
|
2372
|
+
brightYellow: (text) => colorize(text, "brightYellow"),
|
|
2373
|
+
brightBlue: (text) => colorize(text, "brightBlue"),
|
|
2374
|
+
brightCyan: (text) => colorize(text, "brightCyan"),
|
|
2375
|
+
dim: (text) => colorize(text, "dim"),
|
|
2376
|
+
bold: (text) => colorize(text, "bold"),
|
|
2377
|
+
underline: (text) => colorize(text, "underline"),
|
|
2378
|
+
italic: (text) => colorize(text, "italic")
|
|
2379
|
+
};
|
|
2380
|
+
var cliConsole = {
|
|
2381
|
+
log(message, ...args) {
|
|
2382
|
+
globalThis.console.log(message, ...args);
|
|
2383
|
+
},
|
|
2384
|
+
info(message, ...args) {
|
|
2385
|
+
globalThis.console.log(colors.cyan("\u2139"), message, ...args);
|
|
2386
|
+
},
|
|
2387
|
+
success(message, ...args) {
|
|
2388
|
+
globalThis.console.log(colors.green("\u2713"), message, ...args);
|
|
2389
|
+
},
|
|
2390
|
+
warn(message, ...args) {
|
|
2391
|
+
globalThis.console.log(colors.yellow("\u26A0"), message, ...args);
|
|
2392
|
+
},
|
|
2393
|
+
error(message, ...args) {
|
|
2394
|
+
globalThis.console.error(colors.red("\u2717"), message, ...args);
|
|
2395
|
+
},
|
|
2396
|
+
debug(message, ...args) {
|
|
2397
|
+
if (process.env.BUENO_VERBOSE === "true") {
|
|
2398
|
+
globalThis.console.log(colors.dim("\u22EF"), colors.dim(message), ...args);
|
|
2399
|
+
}
|
|
2400
|
+
},
|
|
2401
|
+
header(title) {
|
|
2402
|
+
globalThis.console.log();
|
|
2403
|
+
globalThis.console.log(colors.bold(colors.cyan(title)));
|
|
2404
|
+
globalThis.console.log();
|
|
2405
|
+
},
|
|
2406
|
+
subheader(title) {
|
|
2407
|
+
globalThis.console.log();
|
|
2408
|
+
globalThis.console.log(colors.bold(title));
|
|
2409
|
+
},
|
|
2410
|
+
newline() {
|
|
2411
|
+
globalThis.console.log();
|
|
2412
|
+
},
|
|
2413
|
+
clear() {
|
|
2414
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
function formatTable(headers, rows, options = {}) {
|
|
2418
|
+
const padding = options.padding ?? 2;
|
|
2419
|
+
const widths = headers.map((h, i) => {
|
|
2420
|
+
const maxRowWidth = Math.max(...rows.map((r) => r[i]?.length ?? 0));
|
|
2421
|
+
return Math.max(h.length, maxRowWidth);
|
|
2422
|
+
});
|
|
2423
|
+
const pad = " ".repeat(padding);
|
|
2424
|
+
const headerLine = headers.map((h, i) => h.padEnd(widths[i] ?? 0)).join(pad);
|
|
2425
|
+
const separator = widths.map((w) => "\u2500".repeat(w)).join(pad);
|
|
2426
|
+
const rowLines = rows.map((row) => row.map((cell, i) => (cell ?? "").padEnd(widths[i] ?? 0)).join(pad));
|
|
2427
|
+
return [
|
|
2428
|
+
colors.bold(headerLine),
|
|
2429
|
+
colors.dim(separator),
|
|
2430
|
+
...rowLines
|
|
2431
|
+
].join(`
|
|
2432
|
+
`);
|
|
2433
|
+
}
|
|
2434
|
+
function printTable(headers, rows, options) {
|
|
2435
|
+
globalThis.console.log(formatTable(headers, rows, options));
|
|
2436
|
+
}
|
|
2437
|
+
function formatSize(bytes) {
|
|
2438
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
2439
|
+
let size = bytes;
|
|
2440
|
+
let unitIndex = 0;
|
|
2441
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
2442
|
+
size /= 1024;
|
|
2443
|
+
unitIndex++;
|
|
2444
|
+
}
|
|
2445
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
2446
|
+
}
|
|
2447
|
+
function formatDuration(ms) {
|
|
2448
|
+
if (ms < 1000)
|
|
2449
|
+
return `${ms}ms`;
|
|
2450
|
+
if (ms < 60000)
|
|
2451
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
2452
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
911
2453
|
}
|
|
912
|
-
function getDockerComposeTemplate(projectName, database) {
|
|
913
|
-
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
914
|
-
let databaseServices = "";
|
|
915
|
-
let dependsOn = "";
|
|
916
|
-
if (database === "postgresql") {
|
|
917
|
-
databaseServices = `
|
|
918
|
-
# PostgreSQL Database
|
|
919
|
-
postgres:
|
|
920
|
-
image: postgres:16-alpine
|
|
921
|
-
container_name: ${kebabName}-postgres
|
|
922
|
-
restart: unless-stopped
|
|
923
|
-
environment:
|
|
924
|
-
POSTGRES_USER: \${POSTGRES_USER:-postgres}
|
|
925
|
-
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
926
|
-
POSTGRES_DB: \${POSTGRES_DB:-${kebabName}}
|
|
927
|
-
volumes:
|
|
928
|
-
- postgres_data:/var/lib/postgresql/data
|
|
929
|
-
ports:
|
|
930
|
-
- "\${POSTGRES_PORT:-5432}:5432"
|
|
931
|
-
healthcheck:
|
|
932
|
-
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-${kebabName}}"]
|
|
933
|
-
interval: 10s
|
|
934
|
-
timeout: 5s
|
|
935
|
-
retries: 5
|
|
936
|
-
networks:
|
|
937
|
-
- bueno-network
|
|
938
|
-
|
|
939
|
-
`;
|
|
940
|
-
dependsOn = `
|
|
941
|
-
depends_on:
|
|
942
|
-
postgres:
|
|
943
|
-
condition: service_healthy
|
|
944
|
-
`;
|
|
945
|
-
} else if (database === "mysql") {
|
|
946
|
-
databaseServices = `
|
|
947
|
-
# MySQL Database
|
|
948
|
-
mysql:
|
|
949
|
-
image: mysql:8.0
|
|
950
|
-
container_name: ${kebabName}-mysql
|
|
951
|
-
restart: unless-stopped
|
|
952
|
-
environment:
|
|
953
|
-
MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-root}
|
|
954
|
-
MYSQL_USER: \${MYSQL_USER:-mysql}
|
|
955
|
-
MYSQL_PASSWORD: \${MYSQL_PASSWORD:-mysql}
|
|
956
|
-
MYSQL_DATABASE: \${MYSQL_DATABASE:-${kebabName}}
|
|
957
|
-
volumes:
|
|
958
|
-
- mysql_data:/var/lib/mysql
|
|
959
|
-
ports:
|
|
960
|
-
- "\${MYSQL_PORT:-3306}:3306"
|
|
961
|
-
healthcheck:
|
|
962
|
-
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p\${MYSQL_ROOT_PASSWORD:-root}"]
|
|
963
|
-
interval: 10s
|
|
964
|
-
timeout: 5s
|
|
965
|
-
retries: 5
|
|
966
|
-
networks:
|
|
967
|
-
- bueno-network
|
|
968
2454
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
2455
|
+
// src/cli/commands/index.ts
|
|
2456
|
+
class CommandRegistry {
|
|
2457
|
+
commands = new Map;
|
|
2458
|
+
aliases = new Map;
|
|
2459
|
+
register(definition, handler) {
|
|
2460
|
+
this.commands.set(definition.name, {
|
|
2461
|
+
definition,
|
|
2462
|
+
handler
|
|
2463
|
+
});
|
|
2464
|
+
if (definition.alias) {
|
|
2465
|
+
this.aliases.set(definition.alias, definition.name);
|
|
2466
|
+
}
|
|
975
2467
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
2468
|
+
get(name) {
|
|
2469
|
+
const commandName = this.aliases.get(name) ?? name;
|
|
2470
|
+
return this.commands.get(commandName);
|
|
2471
|
+
}
|
|
2472
|
+
has(name) {
|
|
2473
|
+
const commandName = this.aliases.get(name) ?? name;
|
|
2474
|
+
return this.commands.has(commandName);
|
|
2475
|
+
}
|
|
2476
|
+
getAll() {
|
|
2477
|
+
return Array.from(this.commands.values()).map((c) => c.definition);
|
|
2478
|
+
}
|
|
2479
|
+
getCommands() {
|
|
2480
|
+
return new Map(this.commands);
|
|
2481
|
+
}
|
|
2482
|
+
async execute(name, args) {
|
|
2483
|
+
const command = this.get(name);
|
|
2484
|
+
if (!command) {
|
|
2485
|
+
throw new Error(`Unknown command: ${name}`);
|
|
2486
|
+
}
|
|
2487
|
+
await command.handler(args);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
var registry = new CommandRegistry;
|
|
2491
|
+
function defineCommand(definition, handler) {
|
|
2492
|
+
registry.register(definition, handler);
|
|
2493
|
+
}
|
|
990
2494
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
context: .
|
|
996
|
-
dockerfile: Dockerfile
|
|
997
|
-
container_name: ${kebabName}-app
|
|
998
|
-
restart: unless-stopped
|
|
999
|
-
ports:
|
|
1000
|
-
- "\${APP_PORT:-3000}:3000"
|
|
1001
|
-
environment:
|
|
1002
|
-
NODE_ENV: production
|
|
1003
|
-
BUN_ENV: production
|
|
1004
|
-
${databaseEnv}${dependsOn} networks:
|
|
1005
|
-
- bueno-network
|
|
1006
|
-
healthcheck:
|
|
1007
|
-
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
1008
|
-
interval: 30s
|
|
1009
|
-
timeout: 10s
|
|
1010
|
-
retries: 3
|
|
1011
|
-
start_period: 10s
|
|
1012
|
-
${databaseServices}networks:
|
|
1013
|
-
bueno-network:
|
|
1014
|
-
driver: bridge
|
|
1015
|
-
${volumes}
|
|
1016
|
-
`;
|
|
2495
|
+
// src/cli/core/prompt.ts
|
|
2496
|
+
import * as readline from "readline";
|
|
2497
|
+
function isInteractive() {
|
|
2498
|
+
return !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
1017
2499
|
}
|
|
1018
|
-
function
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
2500
|
+
function createRL() {
|
|
2501
|
+
return readline.createInterface({
|
|
2502
|
+
input: process.stdin,
|
|
2503
|
+
output: process.stdout
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
async function prompt(message, options = {}) {
|
|
2507
|
+
const defaultValue = options.default;
|
|
2508
|
+
const promptText = defaultValue ? `${colors.cyan("?")} ${message} ${colors.dim(`(${defaultValue})`)}: ` : `${colors.cyan("?")} ${message}: `;
|
|
2509
|
+
if (!isInteractive()) {
|
|
2510
|
+
return defaultValue ?? "";
|
|
2511
|
+
}
|
|
2512
|
+
return new Promise((resolve) => {
|
|
2513
|
+
const rl = createRL();
|
|
2514
|
+
rl.question(promptText, (answer) => {
|
|
2515
|
+
rl.close();
|
|
2516
|
+
const value = answer.trim() || defaultValue || "";
|
|
2517
|
+
if (options.validate) {
|
|
2518
|
+
const result = options.validate(value);
|
|
2519
|
+
if (result !== true) {
|
|
2520
|
+
const errorMsg = typeof result === "string" ? result : "Invalid value";
|
|
2521
|
+
process.stdout.write(`${colors.red("\u2717")} ${errorMsg}
|
|
2522
|
+
`);
|
|
2523
|
+
prompt(message, options).then(resolve);
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
resolve(value);
|
|
2528
|
+
});
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
async function confirm(message, options = {}) {
|
|
2532
|
+
const defaultValue = options.default ?? false;
|
|
2533
|
+
const hint = defaultValue ? "Y/n" : "y/N";
|
|
2534
|
+
if (!isInteractive()) {
|
|
2535
|
+
return defaultValue;
|
|
2536
|
+
}
|
|
2537
|
+
const answer = await prompt(`${message} ${colors.dim(`(${hint})`)}`, {
|
|
2538
|
+
default: defaultValue ? "y" : "n",
|
|
2539
|
+
validate: (value) => {
|
|
2540
|
+
if (!value)
|
|
2541
|
+
return true;
|
|
2542
|
+
return ["y", "yes", "n", "no"].includes(value.toLowerCase()) || "Please enter y or n";
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
return ["y", "yes"].includes(answer.toLowerCase());
|
|
2546
|
+
}
|
|
2547
|
+
async function select(message, choices, options = {}) {
|
|
2548
|
+
if (!isInteractive()) {
|
|
2549
|
+
return options.default ?? choices[0]?.value;
|
|
2550
|
+
}
|
|
2551
|
+
const pageSize = options.pageSize ?? 10;
|
|
2552
|
+
let selectedIndex = choices.findIndex((c) => c.value === options.default && !c.disabled);
|
|
2553
|
+
if (selectedIndex === -1) {
|
|
2554
|
+
selectedIndex = choices.findIndex((c) => !c.disabled);
|
|
1040
2555
|
}
|
|
1041
|
-
return
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
2556
|
+
return new Promise((resolve) => {
|
|
2557
|
+
process.stdout.write("\x1B[?25l");
|
|
2558
|
+
const render = () => {
|
|
2559
|
+
const lines = Math.min(choices.length, pageSize);
|
|
2560
|
+
process.stdout.write(`\x1B[${lines + 1}A\x1B[0J`);
|
|
2561
|
+
process.stdout.write(`${colors.cyan("?")} ${message}
|
|
2562
|
+
`);
|
|
2563
|
+
const start = Math.max(0, selectedIndex - pageSize + 1);
|
|
2564
|
+
const end = Math.min(choices.length, start + pageSize);
|
|
2565
|
+
for (let i = start;i < end; i++) {
|
|
2566
|
+
const choice = choices[i];
|
|
2567
|
+
if (!choice)
|
|
2568
|
+
continue;
|
|
2569
|
+
const isSelected = i === selectedIndex;
|
|
2570
|
+
const prefix = isSelected ? `${colors.cyan("\u276F")} ` : " ";
|
|
2571
|
+
const name = choice.name ?? choice.value;
|
|
2572
|
+
const text = choice.disabled ? colors.dim(`${name} (disabled)`) : isSelected ? colors.cyan(name) : name;
|
|
2573
|
+
process.stdout.write(`${prefix}${text}
|
|
2574
|
+
`);
|
|
2575
|
+
}
|
|
2576
|
+
};
|
|
2577
|
+
process.stdout.write(`${colors.cyan("?")} ${message}
|
|
2578
|
+
`);
|
|
2579
|
+
render();
|
|
2580
|
+
const stdin = process.stdin;
|
|
2581
|
+
stdin.setRawMode(true);
|
|
2582
|
+
stdin.resume();
|
|
2583
|
+
stdin.setEncoding("utf8");
|
|
2584
|
+
const cleanup = () => {
|
|
2585
|
+
stdin.setRawMode(false);
|
|
2586
|
+
stdin.pause();
|
|
2587
|
+
stdin.removeListener("data", handler);
|
|
2588
|
+
process.stdout.write("\x1B[?25h");
|
|
2589
|
+
};
|
|
2590
|
+
const handler = (key) => {
|
|
2591
|
+
if (key === "\x1B[A" || key === "k") {
|
|
2592
|
+
do {
|
|
2593
|
+
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
2594
|
+
} while (choices[selectedIndex]?.disabled);
|
|
2595
|
+
render();
|
|
2596
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
2597
|
+
do {
|
|
2598
|
+
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
2599
|
+
} while (choices[selectedIndex]?.disabled);
|
|
2600
|
+
render();
|
|
2601
|
+
} else if (key === "\r" || key === `
|
|
2602
|
+
`) {
|
|
2603
|
+
cleanup();
|
|
2604
|
+
const selected = choices[selectedIndex];
|
|
2605
|
+
if (selected) {
|
|
2606
|
+
process.stdout.write(`\x1B[${Math.min(choices.length, pageSize) + 1}A\x1B[0J`);
|
|
2607
|
+
process.stdout.write(`${colors.cyan("?")} ${message} ${colors.cyan(selected.name ?? selected.value)}
|
|
2608
|
+
`);
|
|
2609
|
+
resolve(selected.value);
|
|
2610
|
+
}
|
|
2611
|
+
} else if (key === "\x1B" || key === "\x03") {
|
|
2612
|
+
cleanup();
|
|
2613
|
+
process.stdout.write(`
|
|
2614
|
+
`);
|
|
2615
|
+
process.exit(130);
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
stdin.on("data", handler);
|
|
2619
|
+
});
|
|
1048
2620
|
}
|
|
1049
2621
|
|
|
1050
|
-
// src/cli/
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
let databaseSection = "";
|
|
1054
|
-
let envVars = "";
|
|
1055
|
-
if (database === "postgresql") {
|
|
1056
|
-
databaseSection = `
|
|
1057
|
-
# PostgreSQL Database
|
|
1058
|
-
- type: pserv
|
|
1059
|
-
name: ${kebabName}-db
|
|
1060
|
-
env: docker
|
|
1061
|
-
region: oregon
|
|
1062
|
-
plan: starter
|
|
1063
|
-
envVars:
|
|
1064
|
-
- key: POSTGRES_USER
|
|
1065
|
-
generateValue: true
|
|
1066
|
-
- key: POSTGRES_PASSWORD
|
|
1067
|
-
generateValue: true
|
|
1068
|
-
- key: POSTGRES_DB
|
|
1069
|
-
value: ${kebabName}
|
|
1070
|
-
disk:
|
|
1071
|
-
name: postgres-data
|
|
1072
|
-
mountPath: /var/lib/postgresql/data
|
|
1073
|
-
sizeGB: 10
|
|
1074
|
-
|
|
1075
|
-
`;
|
|
1076
|
-
envVars = `
|
|
1077
|
-
envVars:
|
|
1078
|
-
- key: DATABASE_URL
|
|
1079
|
-
fromDatabase:
|
|
1080
|
-
name: ${kebabName}-db
|
|
1081
|
-
property: connectionString
|
|
1082
|
-
- key: NODE_ENV
|
|
1083
|
-
value: production
|
|
1084
|
-
- key: BUN_ENV
|
|
1085
|
-
value: production
|
|
1086
|
-
`;
|
|
1087
|
-
} else if (database === "mysql") {
|
|
1088
|
-
databaseSection = `
|
|
1089
|
-
# MySQL Database (using Render's managed MySQL)
|
|
1090
|
-
- type: pserv
|
|
1091
|
-
name: ${kebabName}-db
|
|
1092
|
-
env: docker
|
|
1093
|
-
region: oregon
|
|
1094
|
-
plan: starter
|
|
1095
|
-
envVars:
|
|
1096
|
-
- key: MYSQL_ROOT_PASSWORD
|
|
1097
|
-
generateValue: true
|
|
1098
|
-
- key: MYSQL_USER
|
|
1099
|
-
generateValue: true
|
|
1100
|
-
- key: MYSQL_PASSWORD
|
|
1101
|
-
generateValue: true
|
|
1102
|
-
- key: MYSQL_DATABASE
|
|
1103
|
-
value: ${kebabName}
|
|
1104
|
-
disk:
|
|
1105
|
-
name: mysql-data
|
|
1106
|
-
mountPath: /var/lib/mysql
|
|
1107
|
-
sizeGB: 10
|
|
2622
|
+
// src/cli/core/spinner.ts
|
|
2623
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2624
|
+
var SPINNER_INTERVAL = 80;
|
|
1108
2625
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
2626
|
+
class Spinner {
|
|
2627
|
+
text;
|
|
2628
|
+
color;
|
|
2629
|
+
frameIndex = 0;
|
|
2630
|
+
interval = null;
|
|
2631
|
+
isSpinning = false;
|
|
2632
|
+
stream = process.stdout;
|
|
2633
|
+
constructor(options = {}) {
|
|
2634
|
+
this.text = options.text ?? "";
|
|
2635
|
+
this.color = options.color ?? "cyan";
|
|
2636
|
+
}
|
|
2637
|
+
start(text) {
|
|
2638
|
+
if (text)
|
|
2639
|
+
this.text = text;
|
|
2640
|
+
if (this.isSpinning)
|
|
2641
|
+
return this;
|
|
2642
|
+
this.isSpinning = true;
|
|
2643
|
+
this.frameIndex = 0;
|
|
2644
|
+
this.stream.write("\x1B[?25l");
|
|
2645
|
+
this.interval = setInterval(() => {
|
|
2646
|
+
this.render();
|
|
2647
|
+
}, SPINNER_INTERVAL);
|
|
2648
|
+
return this;
|
|
2649
|
+
}
|
|
2650
|
+
update(text) {
|
|
2651
|
+
this.text = text;
|
|
2652
|
+
if (this.isSpinning) {
|
|
2653
|
+
this.render();
|
|
2654
|
+
}
|
|
2655
|
+
return this;
|
|
2656
|
+
}
|
|
2657
|
+
success(text) {
|
|
2658
|
+
return this.stop(colors.green("\u2713"), text);
|
|
2659
|
+
}
|
|
2660
|
+
error(text) {
|
|
2661
|
+
return this.stop(colors.red("\u2717"), text);
|
|
2662
|
+
}
|
|
2663
|
+
warn(text) {
|
|
2664
|
+
return this.stop(colors.yellow("\u26A0"), text);
|
|
2665
|
+
}
|
|
2666
|
+
info(text) {
|
|
2667
|
+
return this.stop(colors.cyan("\u2139"), text);
|
|
2668
|
+
}
|
|
2669
|
+
stop(symbol, text) {
|
|
2670
|
+
if (!this.isSpinning)
|
|
2671
|
+
return this;
|
|
2672
|
+
this.isSpinning = false;
|
|
2673
|
+
if (this.interval) {
|
|
2674
|
+
clearInterval(this.interval);
|
|
2675
|
+
this.interval = null;
|
|
2676
|
+
}
|
|
2677
|
+
this.stream.write("\r\x1B[K");
|
|
2678
|
+
const finalText = text ?? this.text;
|
|
2679
|
+
if (symbol) {
|
|
2680
|
+
this.stream.write(`${symbol} ${finalText}
|
|
2681
|
+
`);
|
|
2682
|
+
} else {
|
|
2683
|
+
this.stream.write(`${finalText}
|
|
2684
|
+
`);
|
|
2685
|
+
}
|
|
2686
|
+
this.stream.write("\x1B[?25h");
|
|
2687
|
+
return this;
|
|
2688
|
+
}
|
|
2689
|
+
clear() {
|
|
2690
|
+
if (!this.isSpinning)
|
|
2691
|
+
return this;
|
|
2692
|
+
this.stream.write("\r\x1B[K");
|
|
2693
|
+
return this;
|
|
2694
|
+
}
|
|
2695
|
+
render() {
|
|
2696
|
+
if (!isColorEnabled()) {
|
|
2697
|
+
const dots = ".".repeat(this.frameIndex % 3 + 1);
|
|
2698
|
+
this.stream.write(`\r${this.text}${dots} `);
|
|
2699
|
+
this.frameIndex++;
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const frame = SPINNER_FRAMES[this.frameIndex % SPINNER_FRAMES.length];
|
|
2703
|
+
const coloredFrame = colors[this.color](frame);
|
|
2704
|
+
this.stream.write(`\r${coloredFrame} ${this.text}`);
|
|
2705
|
+
this.frameIndex++;
|
|
1129
2706
|
}
|
|
1130
|
-
return `# ${projectName} - Render.com Deployment Configuration
|
|
1131
|
-
# https://render.com/docs/blueprint-spec
|
|
1132
|
-
|
|
1133
|
-
services:
|
|
1134
|
-
# Web Service
|
|
1135
|
-
- type: web
|
|
1136
|
-
name: ${kebabName}
|
|
1137
|
-
env: docker
|
|
1138
|
-
region: oregon
|
|
1139
|
-
plan: starter
|
|
1140
|
-
branch: main
|
|
1141
|
-
dockerfilePath: ./Dockerfile
|
|
1142
|
-
# dockerContext: .
|
|
1143
|
-
numInstances: 1
|
|
1144
|
-
healthCheckPath: /health
|
|
1145
|
-
${envVars} # Auto-deploy on push to main branch
|
|
1146
|
-
autoDeploy: true
|
|
1147
|
-
${databaseSection}
|
|
1148
|
-
# Blueprint metadata
|
|
1149
|
-
metadata:
|
|
1150
|
-
name: ${projectName}
|
|
1151
|
-
description: A Bueno application deployed on Render
|
|
1152
|
-
`;
|
|
1153
2707
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
return `# ${projectName} - Fly.io Deployment Configuration
|
|
1157
|
-
# https://fly.io/docs/reference/configuration/
|
|
1158
|
-
|
|
1159
|
-
app = "${kebabName}"
|
|
1160
|
-
primary_region = "sea"
|
|
1161
|
-
|
|
1162
|
-
[build]
|
|
1163
|
-
dockerfile = "Dockerfile"
|
|
1164
|
-
|
|
1165
|
-
[env]
|
|
1166
|
-
NODE_ENV = "production"
|
|
1167
|
-
BUN_ENV = "production"
|
|
1168
|
-
PORT = "3000"
|
|
1169
|
-
|
|
1170
|
-
[http_service]
|
|
1171
|
-
internal_port = 3000
|
|
1172
|
-
force_https = true
|
|
1173
|
-
auto_stop_machines = "stop"
|
|
1174
|
-
auto_start_machines = true
|
|
1175
|
-
min_machines_running = 0
|
|
1176
|
-
processes = ["app"]
|
|
1177
|
-
|
|
1178
|
-
[http_service.concurrency]
|
|
1179
|
-
type = "connections"
|
|
1180
|
-
hard_limit = 100
|
|
1181
|
-
soft_limit = 80
|
|
1182
|
-
|
|
1183
|
-
[[http_service.checks]]
|
|
1184
|
-
grace_period = "10s"
|
|
1185
|
-
interval = "30s"
|
|
1186
|
-
method = "GET"
|
|
1187
|
-
timeout = "5s"
|
|
1188
|
-
path = "/health"
|
|
1189
|
-
|
|
1190
|
-
[http_service.checks.headers]
|
|
1191
|
-
Content-Type = "application/json"
|
|
1192
|
-
|
|
1193
|
-
[[vm]]
|
|
1194
|
-
cpu_kind = "shared"
|
|
1195
|
-
cpus = 1
|
|
1196
|
-
memory_mb = 512
|
|
1197
|
-
|
|
1198
|
-
[[mounts]]
|
|
1199
|
-
source = "data"
|
|
1200
|
-
destination = "/app/data"
|
|
1201
|
-
initial_size = "1GB"
|
|
1202
|
-
|
|
1203
|
-
# Scale configuration
|
|
1204
|
-
# Use: fly scale count 2 # Scale to 2 machines
|
|
1205
|
-
# Use: fly scale vm shared-cpu-2x --memory 1024 # Upgrade VM
|
|
1206
|
-
|
|
1207
|
-
# Secrets (set via: fly secrets set KEY=VALUE)
|
|
1208
|
-
# DATABASE_URL=your-database-url
|
|
1209
|
-
# Any other sensitive environment variables
|
|
1210
|
-
`;
|
|
2708
|
+
function spinner(text, options) {
|
|
2709
|
+
return new Spinner({ text, ...options }).start();
|
|
1211
2710
|
}
|
|
1212
|
-
function getRailwayTomlTemplate(projectName) {
|
|
1213
|
-
const kebabName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
1214
|
-
return `# ${projectName} - Railway Deployment Configuration
|
|
1215
|
-
# https://docs.railway.app/reference/config-as-code
|
|
1216
|
-
|
|
1217
|
-
[build]
|
|
1218
|
-
builder = "DOCKERFILE"
|
|
1219
|
-
dockerfilePath = "Dockerfile"
|
|
1220
|
-
|
|
1221
|
-
[deploy]
|
|
1222
|
-
startCommand = "bun run dist/main.js"
|
|
1223
|
-
healthcheckPath = "/health"
|
|
1224
|
-
healthcheckTimeout = 300
|
|
1225
|
-
restartPolicyType = "ON_FAILURE"
|
|
1226
|
-
restartPolicyMaxRetries = 3
|
|
1227
|
-
|
|
1228
|
-
# Environment variables
|
|
1229
|
-
# Set these in Railway dashboard or via CLI:
|
|
1230
|
-
# railway variables set NODE_ENV=production
|
|
1231
|
-
# railway variables set DATABASE_URL=your-database-url
|
|
1232
|
-
|
|
1233
|
-
[[services]]
|
|
1234
|
-
name = "${kebabName}"
|
|
1235
|
-
|
|
1236
|
-
[services.variables]
|
|
1237
|
-
NODE_ENV = "production"
|
|
1238
|
-
BUN_ENV = "production"
|
|
1239
|
-
PORT = "3000"
|
|
1240
|
-
|
|
1241
|
-
# Health check configuration
|
|
1242
|
-
[[services.healthchecks]]
|
|
1243
|
-
path = "/health"
|
|
1244
|
-
interval = 30
|
|
1245
|
-
timeout = 10
|
|
1246
|
-
threshold = 3
|
|
1247
2711
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
2712
|
+
class ProgressBar {
|
|
2713
|
+
total;
|
|
2714
|
+
width;
|
|
2715
|
+
text;
|
|
2716
|
+
completeChar;
|
|
2717
|
+
incompleteChar;
|
|
2718
|
+
current = 0;
|
|
2719
|
+
stream = process.stdout;
|
|
2720
|
+
constructor(options) {
|
|
2721
|
+
this.total = options.total;
|
|
2722
|
+
this.width = options.width ?? 40;
|
|
2723
|
+
this.text = options.text ?? "";
|
|
2724
|
+
this.completeChar = options.completeChar ?? "\u2588";
|
|
2725
|
+
this.incompleteChar = options.incompleteChar ?? "\u2591";
|
|
2726
|
+
}
|
|
2727
|
+
start() {
|
|
2728
|
+
this.current = 0;
|
|
2729
|
+
this.render();
|
|
2730
|
+
return this;
|
|
2731
|
+
}
|
|
2732
|
+
update(current) {
|
|
2733
|
+
this.current = Math.min(current, this.total);
|
|
2734
|
+
this.render();
|
|
2735
|
+
return this;
|
|
2736
|
+
}
|
|
2737
|
+
increment(amount = 1) {
|
|
2738
|
+
return this.update(this.current + amount);
|
|
2739
|
+
}
|
|
2740
|
+
complete() {
|
|
2741
|
+
this.current = this.total;
|
|
2742
|
+
this.render();
|
|
2743
|
+
this.stream.write(`
|
|
2744
|
+
`);
|
|
2745
|
+
return this;
|
|
2746
|
+
}
|
|
2747
|
+
render() {
|
|
2748
|
+
const percent = this.current / this.total;
|
|
2749
|
+
const completeWidth = Math.round(this.width * percent);
|
|
2750
|
+
const incompleteWidth = this.width - completeWidth;
|
|
2751
|
+
const complete = this.completeChar.repeat(completeWidth);
|
|
2752
|
+
const incomplete = this.incompleteChar.repeat(incompleteWidth);
|
|
2753
|
+
const bar = colors.green(complete) + colors.dim(incomplete);
|
|
2754
|
+
const percentText = `${Math.round(percent * 100)}%`.padStart(4);
|
|
2755
|
+
const line = `\r${this.text} [${bar}] ${percentText} ${this.current}/${this.total}`;
|
|
2756
|
+
this.stream.write(`\r\x1B[K${line}`);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
async function runTasks(tasks) {
|
|
2760
|
+
for (const task of tasks) {
|
|
2761
|
+
const s = spinner(task.text);
|
|
2762
|
+
try {
|
|
2763
|
+
await task.task();
|
|
2764
|
+
s.success();
|
|
2765
|
+
} catch (error) {
|
|
2766
|
+
s.error();
|
|
2767
|
+
throw error;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
1251
2771
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
2772
|
+
// src/cli/utils/fs.ts
|
|
2773
|
+
import * as fs from "fs";
|
|
2774
|
+
import * as path from "path";
|
|
2775
|
+
async function fileExists(filePath) {
|
|
2776
|
+
try {
|
|
2777
|
+
return await Bun.file(filePath).exists();
|
|
2778
|
+
} catch {
|
|
2779
|
+
return false;
|
|
2780
|
+
}
|
|
1259
2781
|
}
|
|
1260
|
-
function
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
2782
|
+
async function createDirectory(dirPath) {
|
|
2783
|
+
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
2784
|
+
}
|
|
2785
|
+
async function readFile(filePath) {
|
|
2786
|
+
return await Bun.file(filePath).text();
|
|
2787
|
+
}
|
|
2788
|
+
async function writeFile(filePath, content) {
|
|
2789
|
+
const dir = path.dirname(filePath);
|
|
2790
|
+
await createDirectory(dir);
|
|
2791
|
+
await Bun.write(filePath, content);
|
|
2792
|
+
}
|
|
2793
|
+
async function deleteDirectory(dirPath) {
|
|
2794
|
+
await fs.promises.rm(dirPath, { recursive: true, force: true });
|
|
2795
|
+
}
|
|
2796
|
+
async function listFiles(dirPath, options = {}) {
|
|
2797
|
+
const files = [];
|
|
2798
|
+
async function walk(dir) {
|
|
2799
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
2800
|
+
for (const entry of entries) {
|
|
2801
|
+
const fullPath = path.join(dir, entry.name);
|
|
2802
|
+
if (entry.isDirectory() && options.recursive) {
|
|
2803
|
+
await walk(fullPath);
|
|
2804
|
+
} else if (entry.isFile()) {
|
|
2805
|
+
if (!options.pattern || options.pattern.test(entry.name)) {
|
|
2806
|
+
files.push(fullPath);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
1270
2810
|
}
|
|
2811
|
+
await walk(dirPath);
|
|
2812
|
+
return files;
|
|
1271
2813
|
}
|
|
1272
|
-
function
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
2814
|
+
async function findFileUp(startDir, fileName, options = {}) {
|
|
2815
|
+
let currentDir = startDir;
|
|
2816
|
+
const stopAt = options.stopAt ?? "/";
|
|
2817
|
+
while (currentDir !== stopAt && currentDir !== "/") {
|
|
2818
|
+
const filePath = path.join(currentDir, fileName);
|
|
2819
|
+
if (await fileExists(filePath)) {
|
|
2820
|
+
return filePath;
|
|
2821
|
+
}
|
|
2822
|
+
currentDir = path.dirname(currentDir);
|
|
2823
|
+
}
|
|
2824
|
+
return null;
|
|
2825
|
+
}
|
|
2826
|
+
async function getProjectRoot(startDir = process.cwd()) {
|
|
2827
|
+
const packageJsonPath = await findFileUp(startDir, "package.json");
|
|
2828
|
+
if (packageJsonPath) {
|
|
2829
|
+
return path.dirname(packageJsonPath);
|
|
2830
|
+
}
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
async function isBuenoProject(dir = process.cwd()) {
|
|
2834
|
+
const root = await getProjectRoot(dir);
|
|
2835
|
+
if (!root)
|
|
2836
|
+
return false;
|
|
2837
|
+
const configPath = path.join(root, "bueno.config.ts");
|
|
2838
|
+
if (await fileExists(configPath))
|
|
2839
|
+
return true;
|
|
2840
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
2841
|
+
if (await fileExists(packageJsonPath)) {
|
|
2842
|
+
const content = await readFile(packageJsonPath);
|
|
2843
|
+
try {
|
|
2844
|
+
const pkg = JSON.parse(content);
|
|
2845
|
+
return !!(pkg.dependencies?.bueno || pkg.devDependencies?.bueno);
|
|
2846
|
+
} catch {
|
|
2847
|
+
return false;
|
|
2848
|
+
}
|
|
1282
2849
|
}
|
|
2850
|
+
return false;
|
|
1283
2851
|
}
|
|
1284
|
-
function
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
2852
|
+
function joinPaths(...paths) {
|
|
2853
|
+
return path.join(...paths);
|
|
2854
|
+
}
|
|
2855
|
+
function processTemplate(template, data) {
|
|
2856
|
+
let result = template;
|
|
2857
|
+
result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_, key, content) => {
|
|
2858
|
+
const value = data[key];
|
|
2859
|
+
return value ? content : "";
|
|
2860
|
+
});
|
|
2861
|
+
result = result.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, (_, key, content) => {
|
|
2862
|
+
const items = data[key];
|
|
2863
|
+
if (!Array.isArray(items))
|
|
2864
|
+
return "";
|
|
2865
|
+
return items.map((item) => {
|
|
2866
|
+
let itemContent = content;
|
|
2867
|
+
if (typeof item === "object" && item !== null) {
|
|
2868
|
+
for (const [k, v] of Object.entries(item)) {
|
|
2869
|
+
itemContent = itemContent.replace(new RegExp(`\\{\\{${k}\\}\\}`, "g"), String(v));
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
return itemContent;
|
|
2873
|
+
}).join("");
|
|
2874
|
+
});
|
|
2875
|
+
const helpers = {
|
|
2876
|
+
camelCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toLowerCase()),
|
|
2877
|
+
pascalCase: (v) => v.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (c) => c.toUpperCase()),
|
|
2878
|
+
kebabCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase(),
|
|
2879
|
+
snakeCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase(),
|
|
2880
|
+
upperCase: (v) => v.toUpperCase(),
|
|
2881
|
+
lowerCase: (v) => v.toLowerCase(),
|
|
2882
|
+
capitalize: (v) => v.charAt(0).toUpperCase() + v.slice(1),
|
|
2883
|
+
pluralize: (v) => {
|
|
2884
|
+
if (v.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].some((e) => v.endsWith(e))) {
|
|
2885
|
+
return v.slice(0, -1) + "ies";
|
|
2886
|
+
}
|
|
2887
|
+
if (v.endsWith("s") || v.endsWith("x") || v.endsWith("z") || v.endsWith("ch") || v.endsWith("sh")) {
|
|
2888
|
+
return v + "es";
|
|
2889
|
+
}
|
|
2890
|
+
return v + "s";
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
for (const [helperName, helperFn] of Object.entries(helpers)) {
|
|
2894
|
+
const regex = new RegExp(`\\{\\{${helperName}\\s+(\\w+)\\}\\}`, "g");
|
|
2895
|
+
result = result.replace(regex, (_, key) => {
|
|
2896
|
+
const value = data[key];
|
|
2897
|
+
if (typeof value === "string") {
|
|
2898
|
+
return helperFn(value);
|
|
2899
|
+
}
|
|
2900
|
+
return String(value);
|
|
2901
|
+
});
|
|
1294
2902
|
}
|
|
2903
|
+
for (const [key, value] of Object.entries(data)) {
|
|
2904
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
2905
|
+
result = result.replace(regex, String(value));
|
|
2906
|
+
}
|
|
2907
|
+
result = result.replace(/^\s*\n/gm, `
|
|
2908
|
+
`);
|
|
2909
|
+
result = result.replace(/\n{3,}/g, `
|
|
2910
|
+
|
|
2911
|
+
`);
|
|
2912
|
+
return result.trim();
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/cli/utils/strings.ts
|
|
2916
|
+
function kebabCase(str) {
|
|
2917
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[-_\s]+/g, "-").toLowerCase();
|
|
1295
2918
|
}
|
|
1296
2919
|
|
|
1297
2920
|
// src/cli/commands/new.ts
|
|
2921
|
+
init_version();
|
|
2922
|
+
init_templates();
|
|
2923
|
+
init_templates();
|
|
2924
|
+
init_templates();
|
|
1298
2925
|
function validateProjectName(name) {
|
|
1299
2926
|
if (!name || name.length === 0) {
|
|
1300
2927
|
return "Project name is required";
|
|
@@ -1310,27 +2937,26 @@ function validateProjectName(name) {
|
|
|
1310
2937
|
}
|
|
1311
2938
|
return true;
|
|
1312
2939
|
}
|
|
1313
|
-
function getPackageJsonTemplate(config) {
|
|
1314
|
-
const dependencies = {
|
|
1315
|
-
|
|
1316
|
-
dependencies
|
|
2940
|
+
function getPackageJsonTemplate(config, template) {
|
|
2941
|
+
const dependencies = {
|
|
2942
|
+
...getBuenoDependency(),
|
|
2943
|
+
...template.dependencies || {}
|
|
2944
|
+
};
|
|
2945
|
+
if (config.link) {
|
|
2946
|
+
delete dependencies["@buenojs/bueno"];
|
|
1317
2947
|
}
|
|
1318
2948
|
const devDependencies = {
|
|
1319
2949
|
"@types/bun": "latest",
|
|
1320
|
-
typescript: "^5.3.0"
|
|
2950
|
+
typescript: "^5.3.0",
|
|
2951
|
+
...template.devDependencies || {}
|
|
1321
2952
|
};
|
|
1322
|
-
if (config.template === "fullstack" || config.template === "default") {
|
|
1323
|
-
dependencies.zod = "^4.0.0";
|
|
1324
|
-
}
|
|
1325
2953
|
const scripts = {
|
|
1326
2954
|
dev: "bun run --watch server/main.ts",
|
|
1327
2955
|
build: "bun build ./server/main.ts --outdir ./dist --target bun",
|
|
1328
2956
|
start: "bun run dist/main.js",
|
|
1329
|
-
test: "bun test"
|
|
2957
|
+
test: "bun test",
|
|
2958
|
+
...template.scripts || {}
|
|
1330
2959
|
};
|
|
1331
|
-
if (config.template === "fullstack") {
|
|
1332
|
-
scripts["dev:frontend"] = "bun run --watch client/index.html";
|
|
1333
|
-
}
|
|
1334
2960
|
return JSON.stringify({
|
|
1335
2961
|
name: kebabCase(config.name),
|
|
1336
2962
|
version: "0.1.0",
|
|
@@ -1359,121 +2985,8 @@ function getTsConfigTemplate() {
|
|
|
1359
2985
|
exclude: ["node_modules", "dist"]
|
|
1360
2986
|
}, null, 2);
|
|
1361
2987
|
}
|
|
1362
|
-
function getMainTemplate(config) {
|
|
1363
|
-
if (config.template === "minimal") {
|
|
1364
|
-
return `import { createServer } from '@buenojs/bueno';
|
|
1365
|
-
|
|
1366
|
-
const app = createServer();
|
|
1367
|
-
|
|
1368
|
-
app.router.get('/', () => {
|
|
1369
|
-
return { message: 'Hello, Bueno!' };
|
|
1370
|
-
});
|
|
1371
|
-
|
|
1372
|
-
await app.listen(3000);
|
|
1373
|
-
`;
|
|
1374
|
-
}
|
|
1375
|
-
return `import { createApp, Module, Controller, Get, Injectable } from '@buenojs/bueno';
|
|
1376
|
-
import type { Context } from '@buenojs/bueno';
|
|
1377
|
-
|
|
1378
|
-
// Services
|
|
1379
|
-
@Injectable()
|
|
1380
|
-
export class AppService {
|
|
1381
|
-
findAll() {
|
|
1382
|
-
return { message: 'Welcome to Bueno!', items: [] };
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
// Controllers
|
|
1387
|
-
@Controller()
|
|
1388
|
-
export class AppController {
|
|
1389
|
-
constructor(private readonly appService: AppService) {}
|
|
1390
|
-
|
|
1391
|
-
@Get()
|
|
1392
|
-
hello() {
|
|
1393
|
-
return new Response(\`<html>
|
|
1394
|
-
<head>
|
|
1395
|
-
<title>Welcome to Bueno</title>
|
|
1396
|
-
<style>
|
|
1397
|
-
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
|
1398
|
-
h1 { color: #2563eb; }
|
|
1399
|
-
code { background: #f3f4f6; padding: 2px 6px; border-radius: 4px; }
|
|
1400
|
-
pre { background: #f3f4f6; padding: 16px; border-radius: 8px; overflow-x: auto; }
|
|
1401
|
-
a { color: #2563eb; }
|
|
1402
|
-
</style>
|
|
1403
|
-
</head>
|
|
1404
|
-
<body>
|
|
1405
|
-
<h1>\uD83C\uDF89 Welcome to Bueno Framework!</h1>
|
|
1406
|
-
<p>Your Bun-native full-stack framework is running successfully.</p>
|
|
1407
|
-
|
|
1408
|
-
<h2>Getting Started</h2>
|
|
1409
|
-
<ul>
|
|
1410
|
-
<li>Edit <code>server/main.ts</code> to modify this app</li>
|
|
1411
|
-
<li>Add routes using the <code>@Get()</code>, <code>@Post()</code> decorators</li>
|
|
1412
|
-
<li>Create services with <code>@Injectable()</code> and inject them in controllers</li>
|
|
1413
|
-
</ul>
|
|
1414
|
-
|
|
1415
|
-
<h2>Documentation</h2>
|
|
1416
|
-
<p>Visit <a href="https://buenojs.dev">https://buenojs.dev</a> for full documentation.</p>
|
|
1417
|
-
|
|
1418
|
-
<h2>Quick Example</h2>
|
|
1419
|
-
<pre><code>@Controller('/api')
|
|
1420
|
-
class MyController {
|
|
1421
|
-
@Get('/users')
|
|
1422
|
-
getUsers() {
|
|
1423
|
-
return { users: [] };
|
|
1424
|
-
}
|
|
1425
|
-
}</code></pre>
|
|
1426
|
-
</body>
|
|
1427
|
-
</html>\`, {
|
|
1428
|
-
headers: { 'Content-Type': 'text/html; charset=utf-8' }
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
@Get('health')
|
|
1433
|
-
health(ctx: Context) {
|
|
1434
|
-
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
// Module
|
|
1439
|
-
@Module({
|
|
1440
|
-
controllers: [AppController],
|
|
1441
|
-
providers: [AppService],
|
|
1442
|
-
})
|
|
1443
|
-
export class AppModule {}
|
|
1444
|
-
|
|
1445
|
-
// Bootstrap
|
|
1446
|
-
const app = createApp(AppModule);
|
|
1447
|
-
await app.listen(3000);
|
|
1448
|
-
`;
|
|
1449
|
-
}
|
|
1450
|
-
function getConfigTemplate(config) {
|
|
1451
|
-
const dbConfig = config.database === "sqlite" ? `{ url: 'sqlite:./data.db' }` : `{ url: process.env.DATABASE_URL ?? '${config.database}://localhost/${kebabCase(config.name)}' }`;
|
|
1452
|
-
return `import { defineConfig } from '@buenojs/bueno';
|
|
1453
|
-
|
|
1454
|
-
export default defineConfig({
|
|
1455
|
-
server: {
|
|
1456
|
-
port: 3000,
|
|
1457
|
-
host: 'localhost',
|
|
1458
|
-
},
|
|
1459
|
-
|
|
1460
|
-
database: ${dbConfig},
|
|
1461
|
-
|
|
1462
|
-
logger: {
|
|
1463
|
-
level: 'info',
|
|
1464
|
-
pretty: true,
|
|
1465
|
-
},
|
|
1466
|
-
|
|
1467
|
-
health: {
|
|
1468
|
-
enabled: true,
|
|
1469
|
-
healthPath: '/health',
|
|
1470
|
-
readyPath: '/ready',
|
|
1471
|
-
},
|
|
1472
|
-
});
|
|
1473
|
-
`;
|
|
1474
|
-
}
|
|
1475
2988
|
function getEnvExampleTemplate(config) {
|
|
1476
|
-
if (config.database === "sqlite") {
|
|
2989
|
+
if (config.database === "none" || config.database === "sqlite") {
|
|
1477
2990
|
return `# Bueno Environment Variables
|
|
1478
2991
|
NODE_ENV=development
|
|
1479
2992
|
`;
|
|
@@ -1519,9 +3032,16 @@ coverage/
|
|
|
1519
3032
|
`;
|
|
1520
3033
|
}
|
|
1521
3034
|
function getReadmeTemplate(config) {
|
|
3035
|
+
const templateDescriptions = {
|
|
3036
|
+
default: "Standard project with modules and database",
|
|
3037
|
+
minimal: "Bare minimum project structure",
|
|
3038
|
+
fullstack: "Full-stack project with SSR and frontend",
|
|
3039
|
+
api: "API-only project without frontend",
|
|
3040
|
+
website: "Static website with SSG"
|
|
3041
|
+
};
|
|
1522
3042
|
return `# ${config.name}
|
|
1523
3043
|
|
|
1524
|
-
A Bueno application.
|
|
3044
|
+
A Bueno application - ${templateDescriptions[config.template]}.
|
|
1525
3045
|
|
|
1526
3046
|
## Getting Started
|
|
1527
3047
|
|
|
@@ -1553,11 +3073,43 @@ bun run start
|
|
|
1553
3073
|
|
|
1554
3074
|
## Learn More
|
|
1555
3075
|
|
|
1556
|
-
- [Bueno Documentation](https://github.
|
|
3076
|
+
- [Bueno Documentation](https://bueno.github.io)
|
|
1557
3077
|
- [Bun Documentation](https://bun.sh/docs)
|
|
1558
3078
|
`;
|
|
1559
3079
|
}
|
|
1560
|
-
|
|
3080
|
+
function getConfigTemplate(config) {
|
|
3081
|
+
let dbConfig = "undefined";
|
|
3082
|
+
if (config.database === "sqlite") {
|
|
3083
|
+
dbConfig = `{ url: 'sqlite:./data.db' }`;
|
|
3084
|
+
} else if (config.database === "postgresql") {
|
|
3085
|
+
dbConfig = `{ url: process.env.DATABASE_URL ?? 'postgresql://localhost/${kebabCase(config.name)}' }`;
|
|
3086
|
+
} else if (config.database === "mysql") {
|
|
3087
|
+
dbConfig = `{ url: process.env.DATABASE_URL ?? 'mysql://localhost/${kebabCase(config.name)}' }`;
|
|
3088
|
+
}
|
|
3089
|
+
return `import { defineConfig } from '@buenojs/bueno';
|
|
3090
|
+
|
|
3091
|
+
export default defineConfig({
|
|
3092
|
+
server: {
|
|
3093
|
+
port: 3000,
|
|
3094
|
+
host: 'localhost',
|
|
3095
|
+
},
|
|
3096
|
+
|
|
3097
|
+
${config.database !== "none" ? `database: ${dbConfig},` : ""}
|
|
3098
|
+
|
|
3099
|
+
logger: {
|
|
3100
|
+
level: 'info',
|
|
3101
|
+
pretty: true,
|
|
3102
|
+
},
|
|
3103
|
+
|
|
3104
|
+
health: {
|
|
3105
|
+
enabled: true,
|
|
3106
|
+
healthPath: '/health',
|
|
3107
|
+
readyPath: '/ready',
|
|
3108
|
+
},
|
|
3109
|
+
});
|
|
3110
|
+
`;
|
|
3111
|
+
}
|
|
3112
|
+
async function createProjectFiles(projectPath, config, templateResult) {
|
|
1561
3113
|
const tasks = [];
|
|
1562
3114
|
tasks.push({
|
|
1563
3115
|
text: "Creating project structure",
|
|
@@ -1572,12 +3124,15 @@ async function createProjectFiles(projectPath, config) {
|
|
|
1572
3124
|
await createDirectory(joinPaths(projectPath, "server", "config"));
|
|
1573
3125
|
await createDirectory(joinPaths(projectPath, "tests", "unit"));
|
|
1574
3126
|
await createDirectory(joinPaths(projectPath, "tests", "integration"));
|
|
3127
|
+
for (const dir of templateResult.directories) {
|
|
3128
|
+
await createDirectory(joinPaths(projectPath, dir));
|
|
3129
|
+
}
|
|
1575
3130
|
}
|
|
1576
3131
|
});
|
|
1577
3132
|
tasks.push({
|
|
1578
3133
|
text: "Creating package.json",
|
|
1579
3134
|
task: async () => {
|
|
1580
|
-
await writeFile(joinPaths(projectPath, "package.json"), getPackageJsonTemplate(config));
|
|
3135
|
+
await writeFile(joinPaths(projectPath, "package.json"), getPackageJsonTemplate(config, templateResult));
|
|
1581
3136
|
}
|
|
1582
3137
|
});
|
|
1583
3138
|
tasks.push({
|
|
@@ -1586,18 +3141,22 @@ async function createProjectFiles(projectPath, config) {
|
|
|
1586
3141
|
await writeFile(joinPaths(projectPath, "tsconfig.json"), getTsConfigTemplate());
|
|
1587
3142
|
}
|
|
1588
3143
|
});
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
3144
|
+
for (const file of templateResult.files) {
|
|
3145
|
+
tasks.push({
|
|
3146
|
+
text: `Creating ${file.path}`,
|
|
3147
|
+
task: async () => {
|
|
3148
|
+
await writeFile(joinPaths(projectPath, file.path), file.content);
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
if (config.template !== "website") {
|
|
3153
|
+
tasks.push({
|
|
3154
|
+
text: "Creating bueno.config.ts",
|
|
3155
|
+
task: async () => {
|
|
3156
|
+
await writeFile(joinPaths(projectPath, "bueno.config.ts"), getConfigTemplate(config));
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
1601
3160
|
tasks.push({
|
|
1602
3161
|
text: "Creating .env.example",
|
|
1603
3162
|
task: async () => {
|
|
@@ -1616,11 +3175,11 @@ async function createProjectFiles(projectPath, config) {
|
|
|
1616
3175
|
await writeFile(joinPaths(projectPath, "README.md"), getReadmeTemplate(config));
|
|
1617
3176
|
}
|
|
1618
3177
|
});
|
|
1619
|
-
if (config.docker) {
|
|
3178
|
+
if (config.docker && config.template !== "website") {
|
|
1620
3179
|
tasks.push({
|
|
1621
3180
|
text: "Creating Dockerfile",
|
|
1622
3181
|
task: async () => {
|
|
1623
|
-
await writeFile(joinPaths(projectPath, "Dockerfile"), getDockerfileTemplate(config.name, config.database));
|
|
3182
|
+
await writeFile(joinPaths(projectPath, "Dockerfile"), getDockerfileTemplate(config.name, config.database === "none" ? undefined : config.database));
|
|
1624
3183
|
}
|
|
1625
3184
|
});
|
|
1626
3185
|
tasks.push({
|
|
@@ -1632,13 +3191,13 @@ async function createProjectFiles(projectPath, config) {
|
|
|
1632
3191
|
tasks.push({
|
|
1633
3192
|
text: "Creating docker-compose.yml",
|
|
1634
3193
|
task: async () => {
|
|
1635
|
-
await writeFile(joinPaths(projectPath, "docker-compose.yml"), getDockerComposeTemplate(config.name, config.database));
|
|
3194
|
+
await writeFile(joinPaths(projectPath, "docker-compose.yml"), getDockerComposeTemplate(config.name, config.database === "none" ? undefined : config.database));
|
|
1636
3195
|
}
|
|
1637
3196
|
});
|
|
1638
3197
|
tasks.push({
|
|
1639
3198
|
text: "Creating .env.docker",
|
|
1640
3199
|
task: async () => {
|
|
1641
|
-
await writeFile(joinPaths(projectPath, ".env.docker"), getDockerEnvTemplate(config.name, config.database));
|
|
3200
|
+
await writeFile(joinPaths(projectPath, ".env.docker"), getDockerEnvTemplate(config.name, config.database === "none" ? undefined : config.database));
|
|
1642
3201
|
}
|
|
1643
3202
|
});
|
|
1644
3203
|
}
|
|
@@ -1647,7 +3206,7 @@ async function createProjectFiles(projectPath, config) {
|
|
|
1647
3206
|
tasks.push({
|
|
1648
3207
|
text: `Creating ${filename} for ${getDeployPlatformName(platform)}`,
|
|
1649
3208
|
task: async () => {
|
|
1650
|
-
await writeFile(joinPaths(projectPath, filename), getDeployTemplate(platform, config.name, config.database));
|
|
3209
|
+
await writeFile(joinPaths(projectPath, filename), getDeployTemplate(platform, config.name, config.database === "none" ? "sqlite" : config.database));
|
|
1651
3210
|
}
|
|
1652
3211
|
});
|
|
1653
3212
|
}
|
|
@@ -1704,37 +3263,24 @@ async function handleNew(args) {
|
|
|
1704
3263
|
}
|
|
1705
3264
|
if (!useDefaults && isInteractive()) {
|
|
1706
3265
|
if (!template) {
|
|
1707
|
-
template = await select("Select a template:",
|
|
1708
|
-
{ value: "default", name: "Default - Standard project with modules and database" },
|
|
1709
|
-
{ value: "minimal", name: "Minimal - Bare minimum project structure" },
|
|
1710
|
-
{ value: "fullstack", name: "Fullstack - Full-stack project with SSR and auth" },
|
|
1711
|
-
{ value: "api", name: "API - API-only project without frontend" }
|
|
1712
|
-
], { default: "default" });
|
|
3266
|
+
template = await select("Select a template:", getTemplateOptions(), { default: "default" });
|
|
1713
3267
|
}
|
|
1714
3268
|
if ((template === "fullstack" || template === "default") && !framework) {
|
|
1715
|
-
framework = await select("Select a frontend framework:",
|
|
1716
|
-
{ value: "react", name: "React" },
|
|
1717
|
-
{ value: "vue", name: "Vue" },
|
|
1718
|
-
{ value: "svelte", name: "Svelte" },
|
|
1719
|
-
{ value: "solid", name: "Solid" }
|
|
1720
|
-
], { default: "react" });
|
|
3269
|
+
framework = await select("Select a frontend framework:", getFrontendOptions(), { default: "react" });
|
|
1721
3270
|
}
|
|
1722
|
-
if (!database) {
|
|
1723
|
-
database = await select("Select a database:",
|
|
1724
|
-
{ value: "sqlite", name: "SQLite - Local file-based database" },
|
|
1725
|
-
{ value: "postgresql", name: "PostgreSQL - Production-ready relational database" },
|
|
1726
|
-
{ value: "mysql", name: "MySQL - Popular relational database" }
|
|
1727
|
-
], { default: "sqlite" });
|
|
3271
|
+
if (template !== "website" && !database) {
|
|
3272
|
+
database = await select("Select a database:", getDatabaseOptions(), { default: "sqlite" });
|
|
1728
3273
|
}
|
|
1729
3274
|
}
|
|
1730
3275
|
template = template || "default";
|
|
1731
3276
|
framework = framework || "react";
|
|
1732
3277
|
database = database || "sqlite";
|
|
3278
|
+
const isWebsite = template === "website";
|
|
1733
3279
|
const config = {
|
|
1734
3280
|
name,
|
|
1735
3281
|
template,
|
|
1736
|
-
framework,
|
|
1737
|
-
database,
|
|
3282
|
+
framework: isWebsite ? "none" : framework,
|
|
3283
|
+
database: isWebsite ? "none" : database,
|
|
1738
3284
|
skipInstall,
|
|
1739
3285
|
skipGit,
|
|
1740
3286
|
docker,
|
|
@@ -1748,18 +3294,20 @@ async function handleNew(args) {
|
|
|
1748
3294
|
cliConsole.header(`Creating a new Bueno project: ${colors.cyan(name)}`);
|
|
1749
3295
|
const rows = [
|
|
1750
3296
|
["Template", template],
|
|
1751
|
-
["Framework", framework],
|
|
1752
|
-
["Database", database],
|
|
3297
|
+
["Framework", isWebsite ? "N/A (Static Site)" : framework],
|
|
3298
|
+
["Database", isWebsite ? "N/A" : database],
|
|
1753
3299
|
["Docker", docker ? colors.green("Yes") : colors.red("No")],
|
|
1754
3300
|
["Deploy", deploy.length > 0 ? colors.green(deploy.map(getDeployPlatformName).join(", ")) : colors.red("None")],
|
|
1755
3301
|
["Install dependencies", skipInstall ? colors.red("No") : colors.green("Yes")],
|
|
1756
|
-
["Use local package", link ? colors.green("Yes (bun link)") : colors.red("No")]
|
|
1757
|
-
["Initialize git", skipGit ? colors.red("No") : colors.green("Yes")]
|
|
3302
|
+
["Use local package", link ? colors.green("Yes (bun link)") : colors.red("No")]
|
|
1758
3303
|
];
|
|
1759
3304
|
printTable(["Setting", "Value"], rows);
|
|
1760
3305
|
cliConsole.log("");
|
|
3306
|
+
const { getProjectTemplate: getProjectTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
3307
|
+
const templateFn = getProjectTemplate2(template);
|
|
3308
|
+
const templateResult = templateFn(config);
|
|
1761
3309
|
cliConsole.subheader("Creating project files...");
|
|
1762
|
-
await createProjectFiles(projectPath, config);
|
|
3310
|
+
await createProjectFiles(projectPath, config, templateResult);
|
|
1763
3311
|
if (!skipInstall) {
|
|
1764
3312
|
cliConsole.subheader("Installing dependencies...");
|
|
1765
3313
|
const installSpinner = spinner("Running bun install...");
|
|
@@ -1798,35 +3346,17 @@ async function handleNew(args) {
|
|
|
1798
3346
|
}
|
|
1799
3347
|
}
|
|
1800
3348
|
}
|
|
1801
|
-
if (!skipGit) {
|
|
1802
|
-
cliConsole.subheader("Initializing git repository...");
|
|
1803
|
-
const gitSpinner = spinner("Running git init...");
|
|
1804
|
-
try {
|
|
1805
|
-
const proc = Bun.spawn(["git", "init"], {
|
|
1806
|
-
cwd: projectPath,
|
|
1807
|
-
stdout: "pipe",
|
|
1808
|
-
stderr: "pipe"
|
|
1809
|
-
});
|
|
1810
|
-
const exitCode = await proc.exited;
|
|
1811
|
-
if (exitCode === 0) {
|
|
1812
|
-
Bun.spawn(["git", "add", "."], { cwd: projectPath });
|
|
1813
|
-
Bun.spawn(["git", "commit", "-m", "Initial commit from Bueno CLI"], {
|
|
1814
|
-
cwd: projectPath
|
|
1815
|
-
});
|
|
1816
|
-
gitSpinner.success("Git repository initialized");
|
|
1817
|
-
} else {
|
|
1818
|
-
gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
|
|
1819
|
-
}
|
|
1820
|
-
} catch {
|
|
1821
|
-
gitSpinner.warn("Failed to initialize git. Run `git init` manually.");
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
3349
|
cliConsole.log("");
|
|
1825
3350
|
cliConsole.success(`Project created successfully!`);
|
|
1826
3351
|
cliConsole.log("");
|
|
1827
3352
|
cliConsole.log("Next steps:");
|
|
1828
3353
|
cliConsole.log(` ${colors.cyan(`cd ${kebabCase(name)}`)}`);
|
|
1829
|
-
|
|
3354
|
+
if (isWebsite) {
|
|
3355
|
+
cliConsole.log(` ${colors.cyan("bun run dev")} - Start development server`);
|
|
3356
|
+
cliConsole.log(` ${colors.cyan("bun run build")} - Build static site`);
|
|
3357
|
+
} else {
|
|
3358
|
+
cliConsole.log(` ${colors.cyan("bun run dev")}`);
|
|
3359
|
+
}
|
|
1830
3360
|
cliConsole.log("");
|
|
1831
3361
|
cliConsole.log(`Documentation: ${colors.dim("https://github.com/sivaraj/bueno")}`);
|
|
1832
3362
|
}
|
|
@@ -1845,19 +3375,19 @@ defineCommand({
|
|
|
1845
3375
|
name: "template",
|
|
1846
3376
|
alias: "t",
|
|
1847
3377
|
type: "string",
|
|
1848
|
-
description: "Project template (default, minimal, fullstack, api)"
|
|
3378
|
+
description: "Project template (default, minimal, fullstack, api, website)"
|
|
1849
3379
|
},
|
|
1850
3380
|
{
|
|
1851
3381
|
name: "framework",
|
|
1852
3382
|
alias: "f",
|
|
1853
3383
|
type: "string",
|
|
1854
|
-
description: "Frontend framework (react, vue, svelte, solid)"
|
|
3384
|
+
description: "Frontend framework (react, vue, svelte, solid, none)"
|
|
1855
3385
|
},
|
|
1856
3386
|
{
|
|
1857
3387
|
name: "database",
|
|
1858
3388
|
alias: "d",
|
|
1859
3389
|
type: "string",
|
|
1860
|
-
description: "Database driver (sqlite, postgresql, mysql)"
|
|
3390
|
+
description: "Database driver (sqlite, postgresql, mysql, none)"
|
|
1861
3391
|
},
|
|
1862
3392
|
{
|
|
1863
3393
|
name: "skip-install",
|
|
@@ -1869,7 +3399,7 @@ defineCommand({
|
|
|
1869
3399
|
name: "skip-git",
|
|
1870
3400
|
type: "boolean",
|
|
1871
3401
|
default: false,
|
|
1872
|
-
description: "Skip git initialization"
|
|
3402
|
+
description: "Skip git initialization (deprecated - git init is no longer automatic)"
|
|
1873
3403
|
},
|
|
1874
3404
|
{
|
|
1875
3405
|
name: "docker",
|
|
@@ -1901,6 +3431,7 @@ defineCommand({
|
|
|
1901
3431
|
"bueno new my-api --template api",
|
|
1902
3432
|
"bueno new my-fullstack --template fullstack --framework react",
|
|
1903
3433
|
"bueno new my-project --database postgresql",
|
|
3434
|
+
"bueno new my-website --template website",
|
|
1904
3435
|
"bueno new my-app --docker",
|
|
1905
3436
|
"bueno new my-app --docker --database postgresql",
|
|
1906
3437
|
"bueno new my-app --deploy render",
|
|
@@ -1914,7 +3445,7 @@ defineCommand({
|
|
|
1914
3445
|
}, handleNew);
|
|
1915
3446
|
|
|
1916
3447
|
// src/cli/commands/generate.ts
|
|
1917
|
-
var
|
|
3448
|
+
var GENERATOR_ALIASES2 = {
|
|
1918
3449
|
c: "controller",
|
|
1919
3450
|
s: "service",
|
|
1920
3451
|
m: "module",
|
|
@@ -2141,10 +3672,10 @@ export default createMigration('{{migrationId}}', '{{migrationName}}')
|
|
|
2141
3672
|
};
|
|
2142
3673
|
return templates[type];
|
|
2143
3674
|
}
|
|
2144
|
-
function
|
|
3675
|
+
function getFileExtension2(type) {
|
|
2145
3676
|
return type === "dto" ? ".dto.ts" : ".ts";
|
|
2146
3677
|
}
|
|
2147
|
-
function
|
|
3678
|
+
function getDefaultDirectory2(type) {
|
|
2148
3679
|
switch (type) {
|
|
2149
3680
|
case "controller":
|
|
2150
3681
|
case "service":
|
|
@@ -2174,7 +3705,7 @@ async function generateFile(config) {
|
|
|
2174
3705
|
throw new CLIError("Not in a Bueno project directory", "NOT_FOUND" /* NOT_FOUND */);
|
|
2175
3706
|
}
|
|
2176
3707
|
const kebabName = kebabCase(name);
|
|
2177
|
-
const defaultDir =
|
|
3708
|
+
const defaultDir = getDefaultDirectory2(type);
|
|
2178
3709
|
let targetDir;
|
|
2179
3710
|
if (customPath) {
|
|
2180
3711
|
targetDir = joinPaths(projectRoot, customPath);
|
|
@@ -2185,7 +3716,7 @@ async function generateFile(config) {
|
|
|
2185
3716
|
} else {
|
|
2186
3717
|
targetDir = joinPaths(projectRoot, "server", defaultDir, kebabName);
|
|
2187
3718
|
}
|
|
2188
|
-
const fileName = type === "migration" ? `${
|
|
3719
|
+
const fileName = type === "migration" ? `${generateMigrationId2()}_${kebabName}${getFileExtension2(type)}` : `${kebabName}${getFileExtension2(type)}`;
|
|
2189
3720
|
const filePath = joinPaths(targetDir, fileName);
|
|
2190
3721
|
if (!force && await fileExists(filePath)) {
|
|
2191
3722
|
if (isInteractive()) {
|
|
@@ -2203,7 +3734,7 @@ async function generateFile(config) {
|
|
|
2203
3734
|
module: module ?? "",
|
|
2204
3735
|
path: customPath ?? kebabName,
|
|
2205
3736
|
service: type === "controller" ? name : "",
|
|
2206
|
-
migrationId:
|
|
3737
|
+
migrationId: generateMigrationId2(),
|
|
2207
3738
|
migrationName: name,
|
|
2208
3739
|
tableName: kebabName
|
|
2209
3740
|
});
|
|
@@ -2218,7 +3749,7 @@ ${colors.bold("File:")} ${filePath}`);
|
|
|
2218
3749
|
}
|
|
2219
3750
|
return filePath;
|
|
2220
3751
|
}
|
|
2221
|
-
function
|
|
3752
|
+
function generateMigrationId2() {
|
|
2222
3753
|
const now = new Date;
|
|
2223
3754
|
const year = now.getFullYear();
|
|
2224
3755
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
@@ -2233,7 +3764,7 @@ async function handleGenerate(args) {
|
|
|
2233
3764
|
if (!typeArg) {
|
|
2234
3765
|
throw new CLIError("Generator type is required. Usage: bueno generate <type> <name>", "INVALID_ARGS" /* INVALID_ARGS */);
|
|
2235
3766
|
}
|
|
2236
|
-
const type =
|
|
3767
|
+
const type = GENERATOR_ALIASES2[typeArg] ?? typeArg;
|
|
2237
3768
|
if (!getTemplate(type)) {
|
|
2238
3769
|
throw new CLIError(`Unknown generator type: ${typeArg}. Available types: controller, service, module, guard, interceptor, pipe, filter, dto, middleware, migration`, "INVALID_ARGS" /* INVALID_ARGS */);
|
|
2239
3770
|
}
|
|
@@ -2324,7 +3855,7 @@ defineCommand({
|
|
|
2324
3855
|
}, handleGenerate);
|
|
2325
3856
|
|
|
2326
3857
|
// src/cli/commands/migration.ts
|
|
2327
|
-
function
|
|
3858
|
+
function generateMigrationId3() {
|
|
2328
3859
|
const now = new Date;
|
|
2329
3860
|
const year = now.getFullYear();
|
|
2330
3861
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
@@ -2370,7 +3901,7 @@ function parseMigrationFile(filename) {
|
|
|
2370
3901
|
}
|
|
2371
3902
|
async function createMigration(name, dryRun) {
|
|
2372
3903
|
const migrationsDir = await getMigrationsDir();
|
|
2373
|
-
const id =
|
|
3904
|
+
const id = generateMigrationId3();
|
|
2374
3905
|
const kebabName = name.toLowerCase().replace(/\s+/g, "-");
|
|
2375
3906
|
const fileName = `${id}_${kebabName}.ts`;
|
|
2376
3907
|
const filePath = joinPaths(migrationsDir, fileName);
|
|
@@ -3211,8 +4742,11 @@ Bueno CLI - Available Commands
|
|
|
3211
4742
|
cliConsole.log(generateGlobalHelpText(registry.getAll()));
|
|
3212
4743
|
}
|
|
3213
4744
|
});
|
|
4745
|
+
// src/cli/utils/index.ts
|
|
4746
|
+
init_version();
|
|
4747
|
+
|
|
3214
4748
|
// src/cli/index.ts
|
|
3215
|
-
var packageJson = JSON.parse(
|
|
4749
|
+
var packageJson = JSON.parse(readFileSync3(join3(import.meta.dir, "../../package.json"), "utf-8"));
|
|
3216
4750
|
var VERSION = packageJson.version;
|
|
3217
4751
|
class CLIError extends Error {
|
|
3218
4752
|
type;
|