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