@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
package/src/cli/utils/fs.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Provides file system operations using Bun's native APIs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import * as fs from
|
|
8
|
-
import * as path from
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Check if a file exists
|
|
@@ -73,7 +73,7 @@ export async function readFile(filePath: string): Promise<string> {
|
|
|
73
73
|
* Read a file as string (sync)
|
|
74
74
|
*/
|
|
75
75
|
export function readFileSync(filePath: string): string {
|
|
76
|
-
return fs.readFileSync(filePath,
|
|
76
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -98,7 +98,7 @@ export function writeFileSync(filePath: string, content: string): void {
|
|
|
98
98
|
const dir = path.dirname(filePath);
|
|
99
99
|
createDirectorySync(dir);
|
|
100
100
|
|
|
101
|
-
fs.writeFileSync(filePath, content,
|
|
101
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
@@ -132,10 +132,7 @@ export function deleteDirectorySync(dirPath: string): void {
|
|
|
132
132
|
/**
|
|
133
133
|
* Copy a file
|
|
134
134
|
*/
|
|
135
|
-
export async function copyFile(
|
|
136
|
-
src: string,
|
|
137
|
-
dest: string,
|
|
138
|
-
): Promise<void> {
|
|
135
|
+
export async function copyFile(src: string, dest: string): Promise<void> {
|
|
139
136
|
// Ensure destination directory exists
|
|
140
137
|
const dir = path.dirname(dest);
|
|
141
138
|
await createDirectory(dir);
|
|
@@ -211,9 +208,9 @@ export async function findFileUp(
|
|
|
211
208
|
options: { stopAt?: string } = {},
|
|
212
209
|
): Promise<string | null> {
|
|
213
210
|
let currentDir = startDir;
|
|
214
|
-
const stopAt = options.stopAt ??
|
|
211
|
+
const stopAt = options.stopAt ?? "/";
|
|
215
212
|
|
|
216
|
-
while (currentDir !== stopAt && currentDir !==
|
|
213
|
+
while (currentDir !== stopAt && currentDir !== "/") {
|
|
217
214
|
const filePath = path.join(currentDir, fileName);
|
|
218
215
|
if (await fileExists(filePath)) {
|
|
219
216
|
return filePath;
|
|
@@ -231,7 +228,7 @@ export async function getProjectRoot(
|
|
|
231
228
|
startDir: string = process.cwd(),
|
|
232
229
|
): Promise<string | null> {
|
|
233
230
|
// Look for package.json as indicator
|
|
234
|
-
const packageJsonPath = await findFileUp(startDir,
|
|
231
|
+
const packageJsonPath = await findFileUp(startDir, "package.json");
|
|
235
232
|
if (packageJsonPath) {
|
|
236
233
|
return path.dirname(packageJsonPath);
|
|
237
234
|
}
|
|
@@ -248,10 +245,10 @@ export async function isBuenoProject(
|
|
|
248
245
|
if (!root) return false;
|
|
249
246
|
|
|
250
247
|
// Check for bueno.config.ts or package.json with bueno dependency
|
|
251
|
-
const configPath = path.join(root,
|
|
248
|
+
const configPath = path.join(root, "bueno.config.ts");
|
|
252
249
|
if (await fileExists(configPath)) return true;
|
|
253
250
|
|
|
254
|
-
const packageJsonPath = path.join(root,
|
|
251
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
255
252
|
if (await fileExists(packageJsonPath)) {
|
|
256
253
|
const content = await readFile(packageJsonPath);
|
|
257
254
|
try {
|
|
@@ -281,9 +278,10 @@ export async function writeJson(
|
|
|
281
278
|
data: unknown,
|
|
282
279
|
options: { pretty?: boolean } = {},
|
|
283
280
|
): Promise<void> {
|
|
284
|
-
const content =
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
const content =
|
|
282
|
+
options.pretty !== false
|
|
283
|
+
? JSON.stringify(data, null, 2)
|
|
284
|
+
: JSON.stringify(data);
|
|
287
285
|
await writeFile(filePath, content);
|
|
288
286
|
}
|
|
289
287
|
|
|
@@ -326,7 +324,7 @@ export function getExtName(filePath: string): string {
|
|
|
326
324
|
* Normalize path separators
|
|
327
325
|
*/
|
|
328
326
|
export function normalizePath(filePath: string): string {
|
|
329
|
-
return filePath.replace(/\\/g,
|
|
327
|
+
return filePath.replace(/\\/g, "/");
|
|
330
328
|
}
|
|
331
329
|
|
|
332
330
|
/**
|
|
@@ -339,10 +337,7 @@ export interface TemplateData {
|
|
|
339
337
|
/**
|
|
340
338
|
* Process a template string
|
|
341
339
|
*/
|
|
342
|
-
export function processTemplate(
|
|
343
|
-
template: string,
|
|
344
|
-
data: TemplateData,
|
|
345
|
-
): string {
|
|
340
|
+
export function processTemplate(template: string, data: TemplateData): string {
|
|
346
341
|
let result = template;
|
|
347
342
|
|
|
348
343
|
// Process conditionals: {{#if key}}...{{/if}}
|
|
@@ -350,7 +345,7 @@ export function processTemplate(
|
|
|
350
345
|
/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
351
346
|
(_, key: string, content: string) => {
|
|
352
347
|
const value = data[key];
|
|
353
|
-
return value ? content :
|
|
348
|
+
return value ? content : "";
|
|
354
349
|
},
|
|
355
350
|
);
|
|
356
351
|
|
|
@@ -359,55 +354,74 @@ export function processTemplate(
|
|
|
359
354
|
/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
|
|
360
355
|
(_, key: string, content: string) => {
|
|
361
356
|
const items = data[key];
|
|
362
|
-
if (!Array.isArray(items)) return
|
|
357
|
+
if (!Array.isArray(items)) return "";
|
|
363
358
|
|
|
364
359
|
return items
|
|
365
360
|
.map((item) => {
|
|
366
361
|
let itemContent = content;
|
|
367
|
-
if (typeof item ===
|
|
362
|
+
if (typeof item === "object" && item !== null) {
|
|
368
363
|
// Replace nested properties
|
|
369
364
|
for (const [k, v] of Object.entries(item)) {
|
|
370
365
|
itemContent = itemContent.replace(
|
|
371
|
-
new RegExp(`\\{\\{${k}\\}\\}`,
|
|
366
|
+
new RegExp(`\\{\\{${k}\\}\\}`, "g"),
|
|
372
367
|
String(v),
|
|
373
368
|
);
|
|
374
369
|
}
|
|
375
370
|
}
|
|
376
371
|
return itemContent;
|
|
377
372
|
})
|
|
378
|
-
.join(
|
|
373
|
+
.join("");
|
|
379
374
|
},
|
|
380
375
|
);
|
|
381
376
|
|
|
382
377
|
// Process simple variables with helpers: {{helperName key}}
|
|
383
378
|
const helpers: Record<string, (v: string) => string> = {
|
|
384
379
|
camelCase: (v) =>
|
|
385
|
-
v
|
|
380
|
+
v
|
|
381
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
382
|
+
.replace(/^(.)/, (c) => c.toLowerCase()),
|
|
386
383
|
pascalCase: (v) =>
|
|
387
|
-
v
|
|
384
|
+
v
|
|
385
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
386
|
+
.replace(/^(.)/, (c) => c.toUpperCase()),
|
|
388
387
|
kebabCase: (v) =>
|
|
389
|
-
v
|
|
388
|
+
v
|
|
389
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
390
|
+
.replace(/[-_\s]+/g, "-")
|
|
391
|
+
.toLowerCase(),
|
|
390
392
|
snakeCase: (v) =>
|
|
391
|
-
v
|
|
393
|
+
v
|
|
394
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
395
|
+
.replace(/[-\s]+/g, "_")
|
|
396
|
+
.toLowerCase(),
|
|
392
397
|
upperCase: (v) => v.toUpperCase(),
|
|
393
398
|
lowerCase: (v) => v.toLowerCase(),
|
|
394
399
|
capitalize: (v) => v.charAt(0).toUpperCase() + v.slice(1),
|
|
395
400
|
pluralize: (v) => {
|
|
396
|
-
if (
|
|
397
|
-
|
|
401
|
+
if (
|
|
402
|
+
v.endsWith("y") &&
|
|
403
|
+
!["ay", "ey", "iy", "oy", "uy"].some((e) => v.endsWith(e))
|
|
404
|
+
) {
|
|
405
|
+
return v.slice(0, -1) + "ies";
|
|
398
406
|
}
|
|
399
|
-
if (
|
|
400
|
-
|
|
407
|
+
if (
|
|
408
|
+
v.endsWith("s") ||
|
|
409
|
+
v.endsWith("x") ||
|
|
410
|
+
v.endsWith("z") ||
|
|
411
|
+
v.endsWith("ch") ||
|
|
412
|
+
v.endsWith("sh")
|
|
413
|
+
) {
|
|
414
|
+
return v + "es";
|
|
401
415
|
}
|
|
402
|
-
return v +
|
|
416
|
+
return v + "s";
|
|
403
417
|
},
|
|
404
418
|
};
|
|
405
419
|
|
|
406
420
|
for (const [helperName, helperFn] of Object.entries(helpers)) {
|
|
407
|
-
const regex = new RegExp(`\\{\\{${helperName}\\s+(\\w+)\\}\\}`,
|
|
421
|
+
const regex = new RegExp(`\\{\\{${helperName}\\s+(\\w+)\\}\\}`, "g");
|
|
408
422
|
result = result.replace(regex, (_, key: string) => {
|
|
409
423
|
const value = data[key];
|
|
410
|
-
if (typeof value ===
|
|
424
|
+
if (typeof value === "string") {
|
|
411
425
|
return helperFn(value);
|
|
412
426
|
}
|
|
413
427
|
return String(value);
|
|
@@ -416,13 +430,13 @@ export function processTemplate(
|
|
|
416
430
|
|
|
417
431
|
// Process simple variables: {{key}}
|
|
418
432
|
for (const [key, value] of Object.entries(data)) {
|
|
419
|
-
const regex = new RegExp(`\\{\\{${key}\\}\\}`,
|
|
433
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
420
434
|
result = result.replace(regex, String(value));
|
|
421
435
|
}
|
|
422
436
|
|
|
423
437
|
// Clean up empty lines left by conditionals
|
|
424
|
-
result = result.replace(/^\s*\n/gm,
|
|
425
|
-
result = result.replace(/\n{3,}/g,
|
|
438
|
+
result = result.replace(/^\s*\n/gm, "\n");
|
|
439
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
426
440
|
|
|
427
441
|
return result.trim();
|
|
428
|
-
}
|
|
442
|
+
}
|
package/src/cli/utils/index.ts
CHANGED
package/src/cli/utils/strings.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export function camelCase(str: string): string {
|
|
11
11
|
return str
|
|
12
|
-
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() :
|
|
12
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
13
13
|
.replace(/^(.)/, (c) => c.toLowerCase());
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ export function camelCase(str: string): string {
|
|
|
18
18
|
*/
|
|
19
19
|
export function pascalCase(str: string): string {
|
|
20
20
|
return str
|
|
21
|
-
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() :
|
|
21
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
22
22
|
.replace(/^(.)/, (c) => c.toUpperCase());
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -27,8 +27,8 @@ export function pascalCase(str: string): string {
|
|
|
27
27
|
*/
|
|
28
28
|
export function kebabCase(str: string): string {
|
|
29
29
|
return str
|
|
30
|
-
.replace(/([a-z])([A-Z])/g,
|
|
31
|
-
.replace(/[-_\s]+/g,
|
|
30
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
31
|
+
.replace(/[-_\s]+/g, "-")
|
|
32
32
|
.toLowerCase();
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -37,8 +37,8 @@ export function kebabCase(str: string): string {
|
|
|
37
37
|
*/
|
|
38
38
|
export function snakeCase(str: string): string {
|
|
39
39
|
return str
|
|
40
|
-
.replace(/([a-z])([A-Z])/g,
|
|
41
|
-
.replace(/[-\s]+/g,
|
|
40
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
41
|
+
.replace(/[-\s]+/g, "_")
|
|
42
42
|
.toLowerCase();
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -67,31 +67,45 @@ export function capitalize(str: string): string {
|
|
|
67
67
|
* Pluralize a word (simple implementation)
|
|
68
68
|
*/
|
|
69
69
|
export function pluralize(word: string): string {
|
|
70
|
-
if (
|
|
71
|
-
|
|
70
|
+
if (
|
|
71
|
+
word.endsWith("y") &&
|
|
72
|
+
!["ay", "ey", "iy", "oy", "uy"].some((e) => word.endsWith(e))
|
|
73
|
+
) {
|
|
74
|
+
return word.slice(0, -1) + "ies";
|
|
72
75
|
}
|
|
73
|
-
if (
|
|
74
|
-
|
|
76
|
+
if (
|
|
77
|
+
word.endsWith("s") ||
|
|
78
|
+
word.endsWith("x") ||
|
|
79
|
+
word.endsWith("z") ||
|
|
80
|
+
word.endsWith("ch") ||
|
|
81
|
+
word.endsWith("sh")
|
|
82
|
+
) {
|
|
83
|
+
return word + "es";
|
|
75
84
|
}
|
|
76
|
-
return word +
|
|
85
|
+
return word + "s";
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/**
|
|
80
89
|
* Singularize a word (simple implementation)
|
|
81
90
|
*/
|
|
82
91
|
export function singularize(word: string): string {
|
|
83
|
-
if (word.endsWith(
|
|
84
|
-
return word.slice(0, -3) +
|
|
92
|
+
if (word.endsWith("ies")) {
|
|
93
|
+
return word.slice(0, -3) + "y";
|
|
85
94
|
}
|
|
86
|
-
if (word.endsWith(
|
|
95
|
+
if (word.endsWith("es")) {
|
|
87
96
|
// Check for s, x, z, ch, sh endings
|
|
88
97
|
const withoutEs = word.slice(0, -2);
|
|
89
|
-
if (
|
|
90
|
-
withoutEs.endsWith(
|
|
98
|
+
if (
|
|
99
|
+
withoutEs.endsWith("s") ||
|
|
100
|
+
withoutEs.endsWith("x") ||
|
|
101
|
+
withoutEs.endsWith("z") ||
|
|
102
|
+
withoutEs.endsWith("ch") ||
|
|
103
|
+
withoutEs.endsWith("sh")
|
|
104
|
+
) {
|
|
91
105
|
return withoutEs;
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
|
-
if (word.endsWith(
|
|
108
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
95
109
|
return word.slice(0, -1);
|
|
96
110
|
}
|
|
97
111
|
return word;
|
|
@@ -116,13 +130,13 @@ export function isValidFileName(str: string): boolean {
|
|
|
116
130
|
*/
|
|
117
131
|
export function truncate(str: string, maxLength: number): string {
|
|
118
132
|
if (str.length <= maxLength) return str;
|
|
119
|
-
return str.slice(0, maxLength - 3) +
|
|
133
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
/**
|
|
123
137
|
* Pad string to center
|
|
124
138
|
*/
|
|
125
|
-
export function padCenter(str: string, length: number, char =
|
|
139
|
+
export function padCenter(str: string, length: number, char = " "): string {
|
|
126
140
|
const padding = length - str.length;
|
|
127
141
|
if (padding <= 0) return str;
|
|
128
142
|
const left = Math.floor(padding / 2);
|
|
@@ -134,7 +148,7 @@ export function padCenter(str: string, length: number, char = ' '): string {
|
|
|
134
148
|
* Remove file extension
|
|
135
149
|
*/
|
|
136
150
|
export function removeExtension(filename: string): string {
|
|
137
|
-
const lastDot = filename.lastIndexOf(
|
|
151
|
+
const lastDot = filename.lastIndexOf(".");
|
|
138
152
|
if (lastDot === -1 || lastDot === 0) return filename;
|
|
139
153
|
return filename.slice(0, lastDot);
|
|
140
154
|
}
|
|
@@ -143,8 +157,8 @@ export function removeExtension(filename: string): string {
|
|
|
143
157
|
* Get file extension
|
|
144
158
|
*/
|
|
145
159
|
export function getExtension(filename: string): string {
|
|
146
|
-
const lastDot = filename.lastIndexOf(
|
|
147
|
-
if (lastDot === -1 || lastDot === 0) return
|
|
160
|
+
const lastDot = filename.lastIndexOf(".");
|
|
161
|
+
if (lastDot === -1 || lastDot === 0) return "";
|
|
148
162
|
return filename.slice(lastDot + 1);
|
|
149
163
|
}
|
|
150
164
|
|
|
@@ -152,8 +166,8 @@ export function getExtension(filename: string): string {
|
|
|
152
166
|
* Generate a unique ID
|
|
153
167
|
*/
|
|
154
168
|
export function generateId(length = 8): string {
|
|
155
|
-
const chars =
|
|
156
|
-
let result =
|
|
169
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
170
|
+
let result = "";
|
|
157
171
|
for (let i = 0; i < length; i++) {
|
|
158
172
|
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
159
173
|
}
|
|
@@ -164,25 +178,25 @@ export function generateId(length = 8): string {
|
|
|
164
178
|
* Escape string for use in template literals
|
|
165
179
|
*/
|
|
166
180
|
export function escapeTemplateString(str: string): string {
|
|
167
|
-
return str.replace(/[`\\$]/g,
|
|
181
|
+
return str.replace(/[`\\$]/g, "\\$&");
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
/**
|
|
171
185
|
* Escape string for use in regular expressions
|
|
172
186
|
*/
|
|
173
187
|
export function escapeRegExp(str: string): string {
|
|
174
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g,
|
|
188
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
/**
|
|
178
192
|
* Indent a multiline string
|
|
179
193
|
*/
|
|
180
194
|
export function indent(str: string, spaces = 2): string {
|
|
181
|
-
const indentation =
|
|
195
|
+
const indentation = " ".repeat(spaces);
|
|
182
196
|
return str
|
|
183
|
-
.split(
|
|
197
|
+
.split("\n")
|
|
184
198
|
.map((line) => (line.trim() ? indentation + line : line))
|
|
185
|
-
.join(
|
|
199
|
+
.join("\n");
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
/**
|
|
@@ -190,8 +204,8 @@ export function indent(str: string, spaces = 2): string {
|
|
|
190
204
|
*/
|
|
191
205
|
export function stripLines(str: string): string {
|
|
192
206
|
return str
|
|
193
|
-
.split(
|
|
207
|
+
.split("\n")
|
|
194
208
|
.map((line) => line.trim())
|
|
195
|
-
.join(
|
|
196
|
-
.replace(/\n{3,}/g,
|
|
197
|
-
}
|
|
209
|
+
.join("\n")
|
|
210
|
+
.replace(/\n{3,}/g, "\n\n");
|
|
211
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Utility
|
|
3
|
+
*
|
|
4
|
+
* Gets the current version of @buenojs/bueno from package.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
|
|
10
|
+
let cachedVersion: string | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the current version of @buenojs/bueno
|
|
14
|
+
* Reads from package.json at runtime
|
|
15
|
+
*/
|
|
16
|
+
export function getBuenoVersion(): string {
|
|
17
|
+
if (cachedVersion) {
|
|
18
|
+
return cachedVersion;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Try to read from the package.json in the bueno package
|
|
23
|
+
const packageJsonPath = join(
|
|
24
|
+
import.meta.dir,
|
|
25
|
+
"..",
|
|
26
|
+
"..",
|
|
27
|
+
"..",
|
|
28
|
+
"package.json",
|
|
29
|
+
);
|
|
30
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
31
|
+
cachedVersion = `^${packageJson.version}`;
|
|
32
|
+
return cachedVersion;
|
|
33
|
+
} catch {
|
|
34
|
+
// Fallback to a default version if package.json can't be read
|
|
35
|
+
console.warn("Could not read version from package.json, using default");
|
|
36
|
+
return "^0.8.0";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the bueno dependency entry for package.json
|
|
42
|
+
*/
|
|
43
|
+
export function getBuenoDependency(): Record<string, string> {
|
|
44
|
+
return {
|
|
45
|
+
"@buenojs/bueno": getBuenoVersion(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable validation for Bueno Framework
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ValidationResult } from "../validation";
|
|
6
|
+
import { validateEnvSync } from "../validation";
|
|
7
|
+
import { envSchema } from "../validation/schemas";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate environment variables against the Bueno schema
|
|
11
|
+
*
|
|
12
|
+
* @param envVars - Environment variables to validate
|
|
13
|
+
* @returns Validation result with transformed values or error details
|
|
14
|
+
*/
|
|
15
|
+
export function validateEnvVars(
|
|
16
|
+
envVars: Record<string, string>,
|
|
17
|
+
): ValidationResult {
|
|
18
|
+
return validateEnvSync(envSchema, envVars);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate and load environment variables from .env files
|
|
23
|
+
*
|
|
24
|
+
* @param options - Options for loading and validating environment variables
|
|
25
|
+
* @returns Validation result with transformed values or error details
|
|
26
|
+
*/
|
|
27
|
+
export async function validateAndLoadEnv(options?: {
|
|
28
|
+
/** Custom list of env files to load */
|
|
29
|
+
files?: string[];
|
|
30
|
+
/** Whether to also load NODE_ENV-specific file */
|
|
31
|
+
loadNodeEnv?: boolean;
|
|
32
|
+
/** Base directory for env files */
|
|
33
|
+
cwd?: string;
|
|
34
|
+
/** Whether to merge with existing Bun.env */
|
|
35
|
+
mergeWithProcess?: boolean;
|
|
36
|
+
}): Promise<ValidationResult> {
|
|
37
|
+
try {
|
|
38
|
+
// Load environment variables from files
|
|
39
|
+
const { loadEnvFiles } = await import("./env");
|
|
40
|
+
const rawEnvVars = await loadEnvFiles(options);
|
|
41
|
+
|
|
42
|
+
// Validate the environment variables
|
|
43
|
+
return validateEnvVars(rawEnvVars);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
issues: [
|
|
48
|
+
{
|
|
49
|
+
message: `Failed to load and validate environment variables: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get validation error messages as a single string
|
|
58
|
+
*
|
|
59
|
+
* @param result - Validation result
|
|
60
|
+
* @returns Formatted error message or null if valid
|
|
61
|
+
*/
|
|
62
|
+
export function getValidationErrorMessage(
|
|
63
|
+
result: ValidationResult,
|
|
64
|
+
): string | null {
|
|
65
|
+
if (result.success) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result.issues.map((issue) => issue.message).join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if required environment variables are set
|
|
74
|
+
*
|
|
75
|
+
* @param envVars - Environment variables to check
|
|
76
|
+
* @returns True if all required variables are present and valid
|
|
77
|
+
*/
|
|
78
|
+
export function hasRequiredEnvVars(envVars: Record<string, string>): boolean {
|
|
79
|
+
const result = validateEnvVars(envVars);
|
|
80
|
+
return result.success;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get missing required environment variables
|
|
85
|
+
*
|
|
86
|
+
* @param envVars - Environment variables to check
|
|
87
|
+
* @returns Array of missing required variable names
|
|
88
|
+
*/
|
|
89
|
+
export function getMissingEnvVars(envVars: Record<string, string>): string[] {
|
|
90
|
+
const result = validateEnvVars(envVars);
|
|
91
|
+
if (result.success) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result.issues
|
|
96
|
+
.filter((issue) =>
|
|
97
|
+
issue.message.includes("This environment variable is required"),
|
|
98
|
+
)
|
|
99
|
+
.map((issue) => issue.path?.[0] || "unknown");
|
|
100
|
+
}
|