@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.
Files changed (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. package/tsconfig.json +11 -3
@@ -4,8 +4,8 @@
4
4
  * Provides file system operations using Bun's native APIs
5
5
  */
6
6
 
7
- import * as fs from 'fs';
8
- import * as path from 'path';
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, 'utf-8');
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, 'utf-8');
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, 'package.json');
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, 'bueno.config.ts');
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, 'package.json');
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 = options.pretty !== false
285
- ? JSON.stringify(data, null, 2)
286
- : JSON.stringify(data);
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 === 'object' && item !== null) {
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}\\}\\}`, 'g'),
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.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : '')).replace(/^(.)/, (c) => c.toLowerCase()),
380
+ v
381
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
382
+ .replace(/^(.)/, (c) => c.toLowerCase()),
386
383
  pascalCase: (v) =>
387
- v.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : '')).replace(/^(.)/, (c) => c.toUpperCase()),
384
+ v
385
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
386
+ .replace(/^(.)/, (c) => c.toUpperCase()),
388
387
  kebabCase: (v) =>
389
- v.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[-_\s]+/g, '-').toLowerCase(),
388
+ v
389
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
390
+ .replace(/[-_\s]+/g, "-")
391
+ .toLowerCase(),
390
392
  snakeCase: (v) =>
391
- v.replace(/([a-z])([A-Z])/g, '$1_$2').replace(/[-\s]+/g, '_').toLowerCase(),
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 (v.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].some((e) => v.endsWith(e))) {
397
- return v.slice(0, -1) + 'ies';
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 (v.endsWith('s') || v.endsWith('x') || v.endsWith('z') || v.endsWith('ch') || v.endsWith('sh')) {
400
- return v + 'es';
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 + 's';
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+)\\}\\}`, 'g');
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 === 'string') {
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}\\}\\}`, 'g');
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, '\n');
425
- result = result.replace(/\n{3,}/g, '\n\n');
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
+ }
@@ -4,5 +4,6 @@
4
4
  * Re-exports all utility functions
5
5
  */
6
6
 
7
- export * from './strings';
8
- export * from './fs';
7
+ export * from "./strings";
8
+ export * from "./fs";
9
+ export * from "./version";
@@ -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, '$1-$2')
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, '$1_$2')
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 (word.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].some((e) => word.endsWith(e))) {
71
- return word.slice(0, -1) + 'ies';
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 (word.endsWith('s') || word.endsWith('x') || word.endsWith('z') || word.endsWith('ch') || word.endsWith('sh')) {
74
- return word + 'es';
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 + 's';
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('ies')) {
84
- return word.slice(0, -3) + 'y';
92
+ if (word.endsWith("ies")) {
93
+ return word.slice(0, -3) + "y";
85
94
  }
86
- if (word.endsWith('es')) {
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 (withoutEs.endsWith('s') || withoutEs.endsWith('x') || withoutEs.endsWith('z') ||
90
- withoutEs.endsWith('ch') || withoutEs.endsWith('sh')) {
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('s') && !word.endsWith('ss')) {
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 = ' '): string {
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 = 'abcdefghijklmnopqrstuvwxyz0123456789';
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 = ' '.repeat(spaces);
195
+ const indentation = " ".repeat(spaces);
182
196
  return str
183
- .split('\n')
197
+ .split("\n")
184
198
  .map((line) => (line.trim() ? indentation + line : line))
185
- .join('\n');
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('\n')
207
+ .split("\n")
194
208
  .map((line) => line.trim())
195
- .join('\n')
196
- .replace(/\n{3,}/g, '\n\n');
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
+ }