@buenojs/bueno 0.8.4 → 0.8.6
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 +264 -17
- package/dist/cli/{index.js → bin.js} +413 -332
- package/dist/container/index.js +273 -0
- package/dist/context/index.js +219 -0
- package/dist/database/index.js +493 -0
- package/dist/frontend/index.js +7697 -0
- package/dist/graphql/index.js +2156 -0
- package/dist/health/index.js +364 -0
- package/dist/i18n/index.js +345 -0
- package/dist/index.js +9694 -5047
- 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 +3411 -0
- package/dist/notification/index.js +484 -0
- package/dist/observability/index.js +331 -0
- package/dist/openapi/index.js +795 -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/llms.txt +231 -0
- package/package.json +125 -27
- package/src/cache/index.ts +2 -1
- package/src/cli/ARCHITECTURE.md +3 -3
- 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 +294 -232
- 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 +37 -18
- package/src/cli/templates/database/mysql.ts +3 -3
- package/src/cli/templates/database/none.ts +2 -2
- package/src/cli/templates/database/postgresql.ts +3 -3
- package/src/cli/templates/database/sqlite.ts +3 -3
- package/src/cli/templates/deploy.ts +29 -26
- package/src/cli/templates/docker.ts +41 -30
- package/src/cli/templates/frontend/index.ts +33 -15
- package/src/cli/templates/frontend/none.ts +2 -2
- package/src/cli/templates/frontend/react.ts +18 -18
- package/src/cli/templates/frontend/solid.ts +15 -15
- package/src/cli/templates/frontend/svelte.ts +17 -17
- package/src/cli/templates/frontend/vue.ts +15 -15
- package/src/cli/templates/generators/index.ts +29 -29
- package/src/cli/templates/generators/types.ts +21 -21
- package/src/cli/templates/index.ts +6 -6
- package/src/cli/templates/project/api.ts +37 -36
- package/src/cli/templates/project/default.ts +25 -25
- package/src/cli/templates/project/fullstack.ts +28 -26
- package/src/cli/templates/project/index.ts +55 -16
- package/src/cli/templates/project/minimal.ts +17 -12
- package/src/cli/templates/project/types.ts +10 -5
- package/src/cli/templates/project/website.ts +15 -15
- package/src/cli/utils/fs.ts +55 -41
- package/src/cli/utils/index.ts +3 -3
- package/src/cli/utils/strings.ts +47 -33
- package/src/cli/utils/version.ts +14 -8
- 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 +566 -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/graphql/built-in-engine.ts +598 -0
- package/src/graphql/context-builder.ts +110 -0
- package/src/graphql/decorators.ts +358 -0
- package/src/graphql/execution-pipeline.ts +227 -0
- package/src/graphql/graphql-module.ts +563 -0
- package/src/graphql/index.ts +101 -0
- package/src/graphql/metadata.ts +237 -0
- package/src/graphql/schema-builder.ts +319 -0
- package/src/graphql/subscription-handler.ts +283 -0
- package/src/graphql/types.ts +324 -0
- 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 +182 -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 +457 -299
- 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/cli.test.ts +19 -19
- package/tests/integration/fullstack.test.ts +4 -4
- package/tests/unit/cli.test.ts +1 -1
- 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/graphql.test.ts +991 -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
package/src/cli/core/prompt.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Falls back to simple input for non-TTY environments
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import * as readline from
|
|
9
|
-
import { colors } from
|
|
8
|
+
import * as readline from "readline";
|
|
9
|
+
import { colors } from "./console";
|
|
10
10
|
|
|
11
11
|
export interface PromptOptions {
|
|
12
12
|
default?: string;
|
|
@@ -55,11 +55,11 @@ export async function prompt(
|
|
|
55
55
|
): Promise<string> {
|
|
56
56
|
const defaultValue = options.default;
|
|
57
57
|
const promptText = defaultValue
|
|
58
|
-
? `${colors.cyan(
|
|
59
|
-
: `${colors.cyan(
|
|
58
|
+
? `${colors.cyan("?")} ${message} ${colors.dim(`(${defaultValue})`)}: `
|
|
59
|
+
: `${colors.cyan("?")} ${message}: `;
|
|
60
60
|
|
|
61
61
|
if (!isInteractive()) {
|
|
62
|
-
return defaultValue ??
|
|
62
|
+
return defaultValue ?? "";
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
return new Promise((resolve) => {
|
|
@@ -68,13 +68,14 @@ export async function prompt(
|
|
|
68
68
|
rl.question(promptText, (answer) => {
|
|
69
69
|
rl.close();
|
|
70
70
|
|
|
71
|
-
const value = answer.trim() || defaultValue ||
|
|
71
|
+
const value = answer.trim() || defaultValue || "";
|
|
72
72
|
|
|
73
73
|
if (options.validate) {
|
|
74
74
|
const result = options.validate(value);
|
|
75
75
|
if (result !== true) {
|
|
76
|
-
const errorMsg =
|
|
77
|
-
|
|
76
|
+
const errorMsg =
|
|
77
|
+
typeof result === "string" ? result : "Invalid value";
|
|
78
|
+
process.stdout.write(`${colors.red("✗")} ${errorMsg}\n`);
|
|
78
79
|
// Re-prompt on validation failure
|
|
79
80
|
prompt(message, options).then(resolve);
|
|
80
81
|
return;
|
|
@@ -94,22 +95,24 @@ export async function confirm(
|
|
|
94
95
|
options: ConfirmOptions = {},
|
|
95
96
|
): Promise<boolean> {
|
|
96
97
|
const defaultValue = options.default ?? false;
|
|
97
|
-
const hint = defaultValue ?
|
|
98
|
+
const hint = defaultValue ? "Y/n" : "y/N";
|
|
98
99
|
|
|
99
100
|
if (!isInteractive()) {
|
|
100
101
|
return defaultValue;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
const answer = await prompt(`${message} ${colors.dim(`(${hint})`)}`, {
|
|
104
|
-
default: defaultValue ?
|
|
105
|
+
default: defaultValue ? "y" : "n",
|
|
105
106
|
validate: (value) => {
|
|
106
107
|
if (!value) return true;
|
|
107
|
-
return
|
|
108
|
-
|
|
108
|
+
return (
|
|
109
|
+
["y", "yes", "n", "no"].includes(value.toLowerCase()) ||
|
|
110
|
+
"Please enter y or n"
|
|
111
|
+
);
|
|
109
112
|
},
|
|
110
113
|
});
|
|
111
114
|
|
|
112
|
-
return [
|
|
115
|
+
return ["y", "yes"].includes(answer.toLowerCase());
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
/**
|
|
@@ -121,7 +124,7 @@ export async function select<T extends string>(
|
|
|
121
124
|
options: SelectOptions<T> = {},
|
|
122
125
|
): Promise<T> {
|
|
123
126
|
if (!isInteractive()) {
|
|
124
|
-
return options.default ?? choices[0]?.value as T;
|
|
127
|
+
return options.default ?? (choices[0]?.value as T);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
const pageSize = options.pageSize ?? 10;
|
|
@@ -134,7 +137,7 @@ export async function select<T extends string>(
|
|
|
134
137
|
|
|
135
138
|
return new Promise((resolve) => {
|
|
136
139
|
// Hide cursor
|
|
137
|
-
process.stdout.write(
|
|
140
|
+
process.stdout.write("\x1b[?25l");
|
|
138
141
|
|
|
139
142
|
const render = () => {
|
|
140
143
|
// Clear previous output
|
|
@@ -142,7 +145,7 @@ export async function select<T extends string>(
|
|
|
142
145
|
process.stdout.write(`\x1b[${lines + 1}A\x1b[0J`);
|
|
143
146
|
|
|
144
147
|
// Render prompt
|
|
145
|
-
process.stdout.write(`${colors.cyan(
|
|
148
|
+
process.stdout.write(`${colors.cyan("?")} ${message}\n`);
|
|
146
149
|
|
|
147
150
|
// Render choices
|
|
148
151
|
const start = Math.max(0, selectedIndex - pageSize + 1);
|
|
@@ -153,7 +156,7 @@ export async function select<T extends string>(
|
|
|
153
156
|
if (!choice) continue;
|
|
154
157
|
|
|
155
158
|
const isSelected = i === selectedIndex;
|
|
156
|
-
const prefix = isSelected ? `${colors.cyan(
|
|
159
|
+
const prefix = isSelected ? `${colors.cyan("❯")} ` : " ";
|
|
157
160
|
const name = choice.name ?? choice.value;
|
|
158
161
|
const text = choice.disabled
|
|
159
162
|
? colors.dim(`${name} (disabled)`)
|
|
@@ -166,37 +169,37 @@ export async function select<T extends string>(
|
|
|
166
169
|
};
|
|
167
170
|
|
|
168
171
|
// Initial render
|
|
169
|
-
process.stdout.write(`${colors.cyan(
|
|
172
|
+
process.stdout.write(`${colors.cyan("?")} ${message}\n`);
|
|
170
173
|
render();
|
|
171
174
|
|
|
172
175
|
// Handle key input
|
|
173
176
|
const stdin = process.stdin;
|
|
174
177
|
stdin.setRawMode(true);
|
|
175
178
|
stdin.resume();
|
|
176
|
-
stdin.setEncoding(
|
|
179
|
+
stdin.setEncoding("utf8");
|
|
177
180
|
|
|
178
181
|
const cleanup = () => {
|
|
179
182
|
stdin.setRawMode(false);
|
|
180
183
|
stdin.pause();
|
|
181
|
-
stdin.removeListener(
|
|
184
|
+
stdin.removeListener("data", handler);
|
|
182
185
|
// Show cursor
|
|
183
|
-
process.stdout.write(
|
|
186
|
+
process.stdout.write("\x1b[?25h");
|
|
184
187
|
};
|
|
185
188
|
|
|
186
189
|
const handler = (key: string) => {
|
|
187
|
-
if (key ===
|
|
190
|
+
if (key === "\u001b[A" || key === "k") {
|
|
188
191
|
// Up
|
|
189
192
|
do {
|
|
190
193
|
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
191
194
|
} while (choices[selectedIndex]?.disabled);
|
|
192
195
|
render();
|
|
193
|
-
} else if (key ===
|
|
196
|
+
} else if (key === "\u001b[B" || key === "j") {
|
|
194
197
|
// Down
|
|
195
198
|
do {
|
|
196
199
|
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
197
200
|
} while (choices[selectedIndex]?.disabled);
|
|
198
201
|
render();
|
|
199
|
-
} else if (key ===
|
|
202
|
+
} else if (key === "\r" || key === "\n") {
|
|
200
203
|
// Enter
|
|
201
204
|
cleanup();
|
|
202
205
|
const selected = choices[selectedIndex];
|
|
@@ -205,19 +208,19 @@ export async function select<T extends string>(
|
|
|
205
208
|
`\x1b[${Math.min(choices.length, pageSize) + 1}A\x1b[0J`,
|
|
206
209
|
);
|
|
207
210
|
process.stdout.write(
|
|
208
|
-
`${colors.cyan(
|
|
211
|
+
`${colors.cyan("?")} ${message} ${colors.cyan(selected.name ?? selected.value)}\n`,
|
|
209
212
|
);
|
|
210
213
|
resolve(selected.value);
|
|
211
214
|
}
|
|
212
|
-
} else if (key ===
|
|
215
|
+
} else if (key === "\u001b" || key === "\u0003") {
|
|
213
216
|
// Escape or Ctrl+C
|
|
214
217
|
cleanup();
|
|
215
|
-
process.stdout.write(
|
|
218
|
+
process.stdout.write("\n");
|
|
216
219
|
process.exit(130);
|
|
217
220
|
}
|
|
218
221
|
};
|
|
219
222
|
|
|
220
|
-
stdin.on(
|
|
223
|
+
stdin.on("data", handler);
|
|
221
224
|
});
|
|
222
225
|
}
|
|
223
226
|
|
|
@@ -239,7 +242,7 @@ export async function multiSelect<T extends string>(
|
|
|
239
242
|
|
|
240
243
|
return new Promise((resolve) => {
|
|
241
244
|
// Hide cursor
|
|
242
|
-
process.stdout.write(
|
|
245
|
+
process.stdout.write("\x1b[?25l");
|
|
243
246
|
|
|
244
247
|
const render = () => {
|
|
245
248
|
// Clear previous output
|
|
@@ -247,7 +250,7 @@ export async function multiSelect<T extends string>(
|
|
|
247
250
|
process.stdout.write(`\x1b[${lines + 1}A\x1b[0J`);
|
|
248
251
|
|
|
249
252
|
// Render prompt
|
|
250
|
-
process.stdout.write(`${colors.cyan(
|
|
253
|
+
process.stdout.write(`${colors.cyan("?")} ${message}\n`);
|
|
251
254
|
|
|
252
255
|
// Render choices
|
|
253
256
|
const start = Math.max(0, currentIndex - pageSize + 1);
|
|
@@ -259,8 +262,8 @@ export async function multiSelect<T extends string>(
|
|
|
259
262
|
|
|
260
263
|
const isCurrent = i === currentIndex;
|
|
261
264
|
const isSelected = selected.has(choice.value);
|
|
262
|
-
const checkbox = isSelected ? `${colors.green(
|
|
263
|
-
const prefix = isCurrent ? `${colors.cyan(
|
|
265
|
+
const checkbox = isSelected ? `${colors.green("◉")}` : "○";
|
|
266
|
+
const prefix = isCurrent ? `${colors.cyan("❯")} ` : " ";
|
|
264
267
|
const name = choice.name ?? choice.value;
|
|
265
268
|
const text = choice.disabled
|
|
266
269
|
? colors.dim(`${name} (disabled)`)
|
|
@@ -273,37 +276,37 @@ export async function multiSelect<T extends string>(
|
|
|
273
276
|
};
|
|
274
277
|
|
|
275
278
|
// Initial render
|
|
276
|
-
process.stdout.write(`${colors.cyan(
|
|
279
|
+
process.stdout.write(`${colors.cyan("?")} ${message}\n`);
|
|
277
280
|
render();
|
|
278
281
|
|
|
279
282
|
// Handle key input
|
|
280
283
|
const stdin = process.stdin;
|
|
281
284
|
stdin.setRawMode(true);
|
|
282
285
|
stdin.resume();
|
|
283
|
-
stdin.setEncoding(
|
|
286
|
+
stdin.setEncoding("utf8");
|
|
284
287
|
|
|
285
288
|
const cleanup = () => {
|
|
286
289
|
stdin.setRawMode(false);
|
|
287
290
|
stdin.pause();
|
|
288
|
-
stdin.removeListener(
|
|
291
|
+
stdin.removeListener("data", handler);
|
|
289
292
|
// Show cursor
|
|
290
|
-
process.stdout.write(
|
|
293
|
+
process.stdout.write("\x1b[?25h");
|
|
291
294
|
};
|
|
292
295
|
|
|
293
296
|
const handler = (key: string) => {
|
|
294
|
-
if (key ===
|
|
297
|
+
if (key === "\u001b[A" || key === "k") {
|
|
295
298
|
// Up
|
|
296
299
|
do {
|
|
297
300
|
currentIndex = (currentIndex - 1 + choices.length) % choices.length;
|
|
298
301
|
} while (choices[currentIndex]?.disabled);
|
|
299
302
|
render();
|
|
300
|
-
} else if (key ===
|
|
303
|
+
} else if (key === "\u001b[B" || key === "j") {
|
|
301
304
|
// Down
|
|
302
305
|
do {
|
|
303
306
|
currentIndex = (currentIndex + 1) % choices.length;
|
|
304
307
|
} while (choices[currentIndex]?.disabled);
|
|
305
308
|
render();
|
|
306
|
-
} else if (key ===
|
|
309
|
+
} else if (key === " " || key === "x") {
|
|
307
310
|
// Toggle selection
|
|
308
311
|
const choice = choices[currentIndex];
|
|
309
312
|
if (choice && !choice.disabled) {
|
|
@@ -318,7 +321,7 @@ export async function multiSelect<T extends string>(
|
|
|
318
321
|
}
|
|
319
322
|
render();
|
|
320
323
|
}
|
|
321
|
-
} else if (key ===
|
|
324
|
+
} else if (key === "\r" || key === "\n") {
|
|
322
325
|
// Enter
|
|
323
326
|
cleanup();
|
|
324
327
|
const result = Array.from(selected);
|
|
@@ -327,20 +330,20 @@ export async function multiSelect<T extends string>(
|
|
|
327
330
|
);
|
|
328
331
|
const names = result
|
|
329
332
|
.map((v) => choices.find((c) => c.value === v)?.name ?? v)
|
|
330
|
-
.join(
|
|
333
|
+
.join(", ");
|
|
331
334
|
process.stdout.write(
|
|
332
|
-
`${colors.cyan(
|
|
335
|
+
`${colors.cyan("?")} ${message} ${colors.cyan(names || "none")}\n`,
|
|
333
336
|
);
|
|
334
337
|
resolve(result);
|
|
335
|
-
} else if (key ===
|
|
338
|
+
} else if (key === "\u001b" || key === "\u0003") {
|
|
336
339
|
// Escape or Ctrl+C
|
|
337
340
|
cleanup();
|
|
338
|
-
process.stdout.write(
|
|
341
|
+
process.stdout.write("\n");
|
|
339
342
|
process.exit(130);
|
|
340
343
|
}
|
|
341
344
|
};
|
|
342
345
|
|
|
343
|
-
stdin.on(
|
|
346
|
+
stdin.on("data", handler);
|
|
344
347
|
});
|
|
345
348
|
}
|
|
346
349
|
|
|
@@ -355,8 +358,8 @@ export async function number(
|
|
|
355
358
|
...options,
|
|
356
359
|
validate: (v) => {
|
|
357
360
|
if (!v && options.default) return true;
|
|
358
|
-
const num = parseFloat(v);
|
|
359
|
-
if (isNaN(num)) return
|
|
361
|
+
const num = Number.parseFloat(v);
|
|
362
|
+
if (isNaN(num)) return "Please enter a valid number";
|
|
360
363
|
if (options.min !== undefined && num < options.min) {
|
|
361
364
|
return `Value must be at least ${options.min}`;
|
|
362
365
|
}
|
|
@@ -370,7 +373,7 @@ export async function number(
|
|
|
370
373
|
},
|
|
371
374
|
});
|
|
372
375
|
|
|
373
|
-
return parseFloat(value || options.default ||
|
|
376
|
+
return Number.parseFloat(value || options.default || "0");
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
/**
|
|
@@ -378,47 +381,47 @@ export async function number(
|
|
|
378
381
|
*/
|
|
379
382
|
export async function password(
|
|
380
383
|
message: string,
|
|
381
|
-
options: Omit<PromptOptions,
|
|
384
|
+
options: Omit<PromptOptions, "default"> = {},
|
|
382
385
|
): Promise<string> {
|
|
383
386
|
if (!isInteractive()) {
|
|
384
|
-
return
|
|
387
|
+
return "";
|
|
385
388
|
}
|
|
386
389
|
|
|
387
390
|
return new Promise((resolve) => {
|
|
388
391
|
const stdin = process.stdin;
|
|
389
392
|
const stdout = process.stdout;
|
|
390
393
|
|
|
391
|
-
stdout.write(`${colors.cyan(
|
|
394
|
+
stdout.write(`${colors.cyan("?")} ${message}: `);
|
|
392
395
|
|
|
393
|
-
let value =
|
|
396
|
+
let value = "";
|
|
394
397
|
|
|
395
398
|
stdin.setRawMode(true);
|
|
396
399
|
stdin.resume();
|
|
397
|
-
stdin.setEncoding(
|
|
400
|
+
stdin.setEncoding("utf8");
|
|
398
401
|
|
|
399
402
|
const cleanup = () => {
|
|
400
403
|
stdin.setRawMode(false);
|
|
401
404
|
stdin.pause();
|
|
402
|
-
stdin.removeListener(
|
|
405
|
+
stdin.removeListener("data", handler);
|
|
403
406
|
};
|
|
404
407
|
|
|
405
408
|
const handler = (key: string) => {
|
|
406
|
-
if (key ===
|
|
409
|
+
if (key === "\r" || key === "\n") {
|
|
407
410
|
cleanup();
|
|
408
|
-
stdout.write(
|
|
411
|
+
stdout.write("\n");
|
|
409
412
|
resolve(value);
|
|
410
|
-
} else if (key ===
|
|
413
|
+
} else if (key === "\u0003") {
|
|
411
414
|
cleanup();
|
|
412
|
-
stdout.write(
|
|
415
|
+
stdout.write("\n");
|
|
413
416
|
process.exit(130);
|
|
414
|
-
} else if (key ===
|
|
417
|
+
} else if (key === "\u007f" || key === "\b") {
|
|
415
418
|
// Backspace
|
|
416
419
|
value = value.slice(0, -1);
|
|
417
|
-
} else if (key[0] !==
|
|
420
|
+
} else if (key[0] !== "\x1b") {
|
|
418
421
|
value += key;
|
|
419
422
|
}
|
|
420
423
|
};
|
|
421
424
|
|
|
422
|
-
stdin.on(
|
|
425
|
+
stdin.on("data", handler);
|
|
423
426
|
});
|
|
424
|
-
}
|
|
427
|
+
}
|
package/src/cli/core/spinner.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Provides animated spinners and progress indicators
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { colors, isColorEnabled } from
|
|
7
|
+
import { colors, isColorEnabled } from "./console";
|
|
8
8
|
|
|
9
|
-
const SPINNER_FRAMES = [
|
|
9
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
10
10
|
const SPINNER_INTERVAL = 80;
|
|
11
11
|
|
|
12
12
|
export interface SpinnerOptions {
|
|
13
13
|
text?: string;
|
|
14
|
-
color?:
|
|
14
|
+
color?: "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export class Spinner {
|
|
@@ -23,8 +23,8 @@ export class Spinner {
|
|
|
23
23
|
private stream = process.stdout;
|
|
24
24
|
|
|
25
25
|
constructor(options: SpinnerOptions = {}) {
|
|
26
|
-
this.text = options.text ??
|
|
27
|
-
this.color = options.color ??
|
|
26
|
+
this.text = options.text ?? "";
|
|
27
|
+
this.color = options.color ?? "cyan";
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -38,7 +38,7 @@ export class Spinner {
|
|
|
38
38
|
this.frameIndex = 0;
|
|
39
39
|
|
|
40
40
|
// Hide cursor
|
|
41
|
-
this.stream.write(
|
|
41
|
+
this.stream.write("\x1b[?25l");
|
|
42
42
|
|
|
43
43
|
this.interval = setInterval(() => {
|
|
44
44
|
this.render();
|
|
@@ -62,28 +62,28 @@ export class Spinner {
|
|
|
62
62
|
* Stop the spinner with success
|
|
63
63
|
*/
|
|
64
64
|
success(text?: string): this {
|
|
65
|
-
return this.stop(colors.green(
|
|
65
|
+
return this.stop(colors.green("✓"), text);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Stop the spinner with error
|
|
70
70
|
*/
|
|
71
71
|
error(text?: string): this {
|
|
72
|
-
return this.stop(colors.red(
|
|
72
|
+
return this.stop(colors.red("✗"), text);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Stop the spinner with warning
|
|
77
77
|
*/
|
|
78
78
|
warn(text?: string): this {
|
|
79
|
-
return this.stop(colors.yellow(
|
|
79
|
+
return this.stop(colors.yellow("⚠"), text);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Stop the spinner with info
|
|
84
84
|
*/
|
|
85
85
|
info(text?: string): this {
|
|
86
|
-
return this.stop(colors.cyan(
|
|
86
|
+
return this.stop(colors.cyan("ℹ"), text);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
@@ -100,7 +100,7 @@ export class Spinner {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Clear the line
|
|
103
|
-
this.stream.write(
|
|
103
|
+
this.stream.write("\r\x1b[K");
|
|
104
104
|
|
|
105
105
|
// Write final message
|
|
106
106
|
const finalText = text ?? this.text;
|
|
@@ -111,7 +111,7 @@ export class Spinner {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// Show cursor
|
|
114
|
-
this.stream.write(
|
|
114
|
+
this.stream.write("\x1b[?25h");
|
|
115
115
|
|
|
116
116
|
return this;
|
|
117
117
|
}
|
|
@@ -122,7 +122,7 @@ export class Spinner {
|
|
|
122
122
|
clear(): this {
|
|
123
123
|
if (!this.isSpinning) return this;
|
|
124
124
|
|
|
125
|
-
this.stream.write(
|
|
125
|
+
this.stream.write("\r\x1b[K");
|
|
126
126
|
return this;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -132,7 +132,7 @@ export class Spinner {
|
|
|
132
132
|
private render(): void {
|
|
133
133
|
if (!isColorEnabled()) {
|
|
134
134
|
// Without colors, just show dots
|
|
135
|
-
const dots =
|
|
135
|
+
const dots = ".".repeat((this.frameIndex % 3) + 1);
|
|
136
136
|
this.stream.write(`\r${this.text}${dots} `);
|
|
137
137
|
this.frameIndex++;
|
|
138
138
|
return;
|
|
@@ -149,7 +149,10 @@ export class Spinner {
|
|
|
149
149
|
/**
|
|
150
150
|
* Create and start a spinner
|
|
151
151
|
*/
|
|
152
|
-
export function spinner(
|
|
152
|
+
export function spinner(
|
|
153
|
+
text: string,
|
|
154
|
+
options?: Omit<SpinnerOptions, "text">,
|
|
155
|
+
): Spinner {
|
|
153
156
|
return new Spinner({ text, ...options }).start();
|
|
154
157
|
}
|
|
155
158
|
|
|
@@ -176,9 +179,9 @@ export class ProgressBar {
|
|
|
176
179
|
constructor(options: ProgressBarOptions) {
|
|
177
180
|
this.total = options.total;
|
|
178
181
|
this.width = options.width ?? 40;
|
|
179
|
-
this.text = options.text ??
|
|
180
|
-
this.completeChar = options.completeChar ??
|
|
181
|
-
this.incompleteChar = options.incompleteChar ??
|
|
182
|
+
this.text = options.text ?? "";
|
|
183
|
+
this.completeChar = options.completeChar ?? "█";
|
|
184
|
+
this.incompleteChar = options.incompleteChar ?? "░";
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
/**
|
|
@@ -212,7 +215,7 @@ export class ProgressBar {
|
|
|
212
215
|
complete(): this {
|
|
213
216
|
this.current = this.total;
|
|
214
217
|
this.render();
|
|
215
|
-
this.stream.write(
|
|
218
|
+
this.stream.write("\n");
|
|
216
219
|
return this;
|
|
217
220
|
}
|
|
218
221
|
|
|
@@ -262,4 +265,4 @@ export async function runTasks(tasks: TaskOptions[]): Promise<void> {
|
|
|
262
265
|
throw error;
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
|
-
}
|
|
268
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -4,24 +4,30 @@
|
|
|
4
4
|
* Main entry point for the Bueno CLI
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { readFileSync } from
|
|
8
|
-
import { join } from
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { registry } from "./commands";
|
|
10
|
+
import {
|
|
11
|
+
type ParsedArgs,
|
|
12
|
+
generateGlobalHelpText,
|
|
13
|
+
generateHelpText,
|
|
14
|
+
hasFlag,
|
|
15
|
+
parseArgs,
|
|
16
|
+
} from "./core/args";
|
|
17
|
+
import { cliConsole, colors, setColorEnabled } from "./core/console";
|
|
12
18
|
|
|
13
19
|
// Import commands to register them
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
20
|
+
import "./commands/new";
|
|
21
|
+
import "./commands/generate";
|
|
22
|
+
import "./commands/migration";
|
|
23
|
+
import "./commands/dev";
|
|
24
|
+
import "./commands/build";
|
|
25
|
+
import "./commands/start";
|
|
26
|
+
import "./commands/help";
|
|
21
27
|
|
|
22
28
|
// Read version from package.json dynamically
|
|
23
29
|
const packageJson = JSON.parse(
|
|
24
|
-
readFileSync(join(import.meta.dir,
|
|
30
|
+
readFileSync(join(import.meta.dir, "../../package.json"), "utf-8"),
|
|
25
31
|
);
|
|
26
32
|
const VERSION = packageJson.version;
|
|
27
33
|
|
|
@@ -29,15 +35,15 @@ const VERSION = packageJson.version;
|
|
|
29
35
|
* CLI error types
|
|
30
36
|
*/
|
|
31
37
|
export enum CLIErrorType {
|
|
32
|
-
INVALID_ARGS =
|
|
33
|
-
FILE_EXISTS =
|
|
34
|
-
FILE_NOT_FOUND =
|
|
35
|
-
MODULE_NOT_FOUND =
|
|
36
|
-
TEMPLATE_ERROR =
|
|
37
|
-
DATABASE_ERROR =
|
|
38
|
-
NETWORK_ERROR =
|
|
39
|
-
PERMISSION_ERROR =
|
|
40
|
-
NOT_FOUND =
|
|
38
|
+
INVALID_ARGS = "INVALID_ARGS",
|
|
39
|
+
FILE_EXISTS = "FILE_EXISTS",
|
|
40
|
+
FILE_NOT_FOUND = "FILE_NOT_FOUND",
|
|
41
|
+
MODULE_NOT_FOUND = "MODULE_NOT_FOUND",
|
|
42
|
+
TEMPLATE_ERROR = "TEMPLATE_ERROR",
|
|
43
|
+
DATABASE_ERROR = "DATABASE_ERROR",
|
|
44
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
45
|
+
PERMISSION_ERROR = "PERMISSION_ERROR",
|
|
46
|
+
NOT_FOUND = "NOT_FOUND",
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/**
|
|
@@ -50,38 +56,40 @@ export class CLIError extends Error {
|
|
|
50
56
|
public readonly exitCode = 1,
|
|
51
57
|
) {
|
|
52
58
|
super(message);
|
|
53
|
-
this.name =
|
|
59
|
+
this.name = "CLIError";
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
/**
|
|
58
64
|
* Run the CLI
|
|
59
65
|
*/
|
|
60
|
-
export async function run(
|
|
66
|
+
export async function run(
|
|
67
|
+
argv: string[] = process.argv.slice(2),
|
|
68
|
+
): Promise<void> {
|
|
61
69
|
// Parse arguments
|
|
62
70
|
const args = parseArgs(argv);
|
|
63
71
|
|
|
64
72
|
// Handle global options
|
|
65
|
-
if (hasFlag(args,
|
|
73
|
+
if (hasFlag(args, "no-color")) {
|
|
66
74
|
setColorEnabled(false);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
if (hasFlag(args,
|
|
70
|
-
process.env.BUENO_VERBOSE =
|
|
77
|
+
if (hasFlag(args, "verbose")) {
|
|
78
|
+
process.env.BUENO_VERBOSE = "true";
|
|
71
79
|
}
|
|
72
80
|
|
|
73
|
-
if (hasFlag(args,
|
|
74
|
-
process.env.BUENO_QUIET =
|
|
81
|
+
if (hasFlag(args, "quiet")) {
|
|
82
|
+
process.env.BUENO_QUIET = "true";
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
// Show version
|
|
78
|
-
if (hasFlag(args,
|
|
86
|
+
if (hasFlag(args, "version") || hasFlag(args, "v")) {
|
|
79
87
|
cliConsole.log(`bueno v${VERSION}`);
|
|
80
88
|
process.exit(0);
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
// Show help if no command or help flag
|
|
84
|
-
if (!args.command || hasFlag(args,
|
|
92
|
+
if (!args.command || hasFlag(args, "help") || hasFlag(args, "h")) {
|
|
85
93
|
if (args.command && registry.has(args.command)) {
|
|
86
94
|
// Show command-specific help
|
|
87
95
|
const cmd = registry.get(args.command);
|
|
@@ -105,7 +113,7 @@ export async function run(argv: string[] = process.argv.slice(2)): Promise<void>
|
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
if (error instanceof Error) {
|
|
108
|
-
if (process.env.BUENO_VERBOSE ===
|
|
116
|
+
if (process.env.BUENO_VERBOSE === "true") {
|
|
109
117
|
cliConsole.error(error.stack ?? error.message);
|
|
110
118
|
} else {
|
|
111
119
|
cliConsole.error(error.message);
|
|
@@ -120,14 +128,14 @@ export async function run(argv: string[] = process.argv.slice(2)): Promise<void>
|
|
|
120
128
|
*/
|
|
121
129
|
export async function main(): Promise<void> {
|
|
122
130
|
// Handle Ctrl+C gracefully
|
|
123
|
-
process.on(
|
|
131
|
+
process.on("SIGINT", () => {
|
|
124
132
|
cliConsole.newline();
|
|
125
133
|
process.exit(130);
|
|
126
134
|
});
|
|
127
135
|
|
|
128
136
|
// Handle unhandled rejections
|
|
129
|
-
process.on(
|
|
130
|
-
cliConsole.error(
|
|
137
|
+
process.on("unhandledRejection", (reason) => {
|
|
138
|
+
cliConsole.error("Unhandled rejection:", reason);
|
|
131
139
|
process.exit(1);
|
|
132
140
|
});
|
|
133
141
|
|
|
@@ -135,6 +143,6 @@ export async function main(): Promise<void> {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
// Export for programmatic use
|
|
138
|
-
export { registry, defineCommand, command } from
|
|
139
|
-
export * from
|
|
140
|
-
export * from
|
|
146
|
+
export { registry, defineCommand, command } from "./commands";
|
|
147
|
+
export * from "./core";
|
|
148
|
+
export * from "./utils";
|