@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/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
|
+
}
|
package/src/cli/utils/version.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Gets the current version of @buenojs/bueno from package.json
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { readFileSync } from
|
|
8
|
-
import { join } from
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
9
|
|
|
10
10
|
let cachedVersion: string | null = null;
|
|
11
11
|
|
|
@@ -20,14 +20,20 @@ export function getBuenoVersion(): string {
|
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
// Try to read from the package.json in the bueno package
|
|
23
|
-
const packageJsonPath = join(
|
|
24
|
-
|
|
23
|
+
const packageJsonPath = join(
|
|
24
|
+
import.meta.dir,
|
|
25
|
+
"..",
|
|
26
|
+
"..",
|
|
27
|
+
"..",
|
|
28
|
+
"package.json",
|
|
29
|
+
);
|
|
30
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
25
31
|
cachedVersion = `^${packageJson.version}`;
|
|
26
32
|
return cachedVersion;
|
|
27
33
|
} catch {
|
|
28
34
|
// Fallback to a default version if package.json can't be read
|
|
29
|
-
console.warn(
|
|
30
|
-
return
|
|
35
|
+
console.warn("Could not read version from package.json, using default");
|
|
36
|
+
return "^0.8.0";
|
|
31
37
|
}
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -36,6 +42,6 @@ export function getBuenoVersion(): string {
|
|
|
36
42
|
*/
|
|
37
43
|
export function getBuenoDependency(): Record<string, string> {
|
|
38
44
|
return {
|
|
39
|
-
|
|
45
|
+
"@buenojs/bueno": getBuenoVersion(),
|
|
40
46
|
};
|
|
41
|
-
}
|
|
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
|
+
}
|
package/src/config/env.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Environment variable handling for Bueno Framework
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { setNestedValue } from "./merge";
|
|
5
6
|
import type { BuenoConfig, DeepPartial, EnvMapping } from "./types";
|
|
6
7
|
import { ENV_MAPPINGS } from "./types";
|
|
7
|
-
import { setNestedValue } from "./merge";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Environment variable source information
|
|
@@ -168,6 +168,113 @@ export async function loadEnvFiles(options?: {
|
|
|
168
168
|
return result;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// ============= Environment Variable Validation =============
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validate environment variables before loading
|
|
175
|
+
*
|
|
176
|
+
* @param envVars - Environment variables to validate
|
|
177
|
+
* @returns Validation result with transformed values or error details
|
|
178
|
+
*/
|
|
179
|
+
export async function validateEnvVars(
|
|
180
|
+
envVars: Record<string, string>,
|
|
181
|
+
): Promise<ValidationResult> {
|
|
182
|
+
try {
|
|
183
|
+
const { validateEnvVars } = await import("./env-validation");
|
|
184
|
+
return validateEnvVars(envVars);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
issues: [
|
|
189
|
+
{
|
|
190
|
+
message: `Failed to validate environment variables: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Load and validate environment variables with detailed error reporting
|
|
199
|
+
*
|
|
200
|
+
* @param options - Options for loading and validating environment variables
|
|
201
|
+
* @returns Loaded environment data with validation result
|
|
202
|
+
*/
|
|
203
|
+
export async function loadAndValidateEnv(options?: {
|
|
204
|
+
/** Custom list of env files to load */
|
|
205
|
+
files?: string[];
|
|
206
|
+
/** Whether to also load NODE_ENV-specific file */
|
|
207
|
+
loadNodeEnv?: boolean;
|
|
208
|
+
/** Base directory for env files */
|
|
209
|
+
cwd?: string;
|
|
210
|
+
/** Whether to merge with existing Bun.env */
|
|
211
|
+
mergeWithProcess?: boolean;
|
|
212
|
+
/** Custom environment variable mappings */
|
|
213
|
+
mappings?: EnvMapping[];
|
|
214
|
+
}): Promise<{ loaded: LoadedEnv; valid: boolean; errors?: string }> {
|
|
215
|
+
try {
|
|
216
|
+
// Load environment variables from files
|
|
217
|
+
const fileVars = await loadEnvFiles(options);
|
|
218
|
+
|
|
219
|
+
// Validate the environment variables
|
|
220
|
+
const validationResult = await validateEnvVars(fileVars);
|
|
221
|
+
|
|
222
|
+
// Merge with Bun.env if requested
|
|
223
|
+
const raw: Record<string, string> =
|
|
224
|
+
options?.mergeWithProcess !== false
|
|
225
|
+
? {
|
|
226
|
+
...fileVars,
|
|
227
|
+
...Object.fromEntries(
|
|
228
|
+
Object.entries(Bun.env).filter(([, v]) => v !== undefined) as [
|
|
229
|
+
string,
|
|
230
|
+
string,
|
|
231
|
+
][],
|
|
232
|
+
),
|
|
233
|
+
}
|
|
234
|
+
: fileVars;
|
|
235
|
+
|
|
236
|
+
// Transform to config
|
|
237
|
+
const config = envToConfig(raw, options?.mappings);
|
|
238
|
+
|
|
239
|
+
// Track sources
|
|
240
|
+
const sources = new Map<string, EnvSourceInfo>();
|
|
241
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
242
|
+
sources.set(name, {
|
|
243
|
+
name,
|
|
244
|
+
value,
|
|
245
|
+
source: fileVars[name] !== undefined ? ".env file" : "process",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Set loaded vars to Bun.env
|
|
250
|
+
for (const [key, value] of Object.entries(fileVars)) {
|
|
251
|
+
if (Bun.env[key] === undefined) {
|
|
252
|
+
Bun.env[key] = value;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const loaded: LoadedEnv = { raw, config, sources };
|
|
257
|
+
|
|
258
|
+
if (!validationResult.success) {
|
|
259
|
+
return {
|
|
260
|
+
loaded,
|
|
261
|
+
valid: false,
|
|
262
|
+
errors: validationResult.issues
|
|
263
|
+
.map((issue) => issue.message)
|
|
264
|
+
.join("\n"),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { loaded, valid: true };
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
loaded: { raw: {}, config: {}, sources: new Map() },
|
|
272
|
+
valid: false,
|
|
273
|
+
errors: `Failed to load and validate environment variables: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
171
278
|
/**
|
|
172
279
|
* Get environment variable value from Bun.env
|
|
173
280
|
*/
|
|
@@ -233,36 +340,18 @@ export async function loadEnv(options?: {
|
|
|
233
340
|
/** Custom environment variable mappings */
|
|
234
341
|
mappings?: EnvMapping[];
|
|
235
342
|
}): Promise<LoadedEnv> {
|
|
236
|
-
// Load
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Transform to config
|
|
246
|
-
const config = envToConfig(raw, options?.mappings);
|
|
247
|
-
|
|
248
|
-
// Track sources
|
|
249
|
-
const sources = new Map<string, EnvSourceInfo>();
|
|
250
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
251
|
-
sources.set(name, {
|
|
252
|
-
name,
|
|
253
|
-
value,
|
|
254
|
-
source: fileVars[name] !== undefined ? ".env file" : "process",
|
|
255
|
-
});
|
|
343
|
+
// Load and validate environment variables
|
|
344
|
+
const result = await loadAndValidateEnv(options);
|
|
345
|
+
|
|
346
|
+
if (!result.valid) {
|
|
347
|
+
console.error("Environment variable validation failed:");
|
|
348
|
+
console.error(result.errors);
|
|
349
|
+
throw new Error(
|
|
350
|
+
"Environment variable validation failed. Check the error messages above.",
|
|
351
|
+
);
|
|
256
352
|
}
|
|
257
353
|
|
|
258
|
-
|
|
259
|
-
for (const [key, value] of Object.entries(fileVars)) {
|
|
260
|
-
if (Bun.env[key] === undefined) {
|
|
261
|
-
Bun.env[key] = value;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return { raw, config, sources };
|
|
354
|
+
return result.loaded;
|
|
266
355
|
}
|
|
267
356
|
|
|
268
357
|
/**
|
|
@@ -324,7 +413,7 @@ export function parseEnvBoolean(value: string): boolean {
|
|
|
324
413
|
* Parse a number environment variable
|
|
325
414
|
*/
|
|
326
415
|
export function parseEnvNumber(value: string): number {
|
|
327
|
-
const num = parseInt(value, 10);
|
|
416
|
+
const num = Number.parseInt(value, 10);
|
|
328
417
|
if (isNaN(num)) {
|
|
329
418
|
throw new Error(`Invalid number: ${value}`);
|
|
330
419
|
}
|
|
@@ -351,22 +440,58 @@ export function parseEnvArray(value: string): string[] {
|
|
|
351
440
|
export const envConfigMapping: EnvConfigMapping[] = [
|
|
352
441
|
{ envVar: "BUENO_PORT", configKey: "server.port", transform: parseEnvNumber },
|
|
353
442
|
{ envVar: "BUENO_HOST", configKey: "server.host" },
|
|
354
|
-
{
|
|
443
|
+
{
|
|
444
|
+
envVar: "BUENO_DEV",
|
|
445
|
+
configKey: "server.development",
|
|
446
|
+
transform: parseEnvBoolean,
|
|
447
|
+
},
|
|
355
448
|
{ envVar: "DATABASE_URL", configKey: "database.url" },
|
|
356
|
-
{
|
|
449
|
+
{
|
|
450
|
+
envVar: "DATABASE_POOL_SIZE",
|
|
451
|
+
configKey: "database.poolSize",
|
|
452
|
+
transform: parseEnvNumber,
|
|
453
|
+
},
|
|
357
454
|
{ envVar: "REDIS_URL", configKey: "cache.url" },
|
|
358
455
|
{ envVar: "CACHE_DRIVER", configKey: "cache.driver" },
|
|
359
456
|
{ envVar: "CACHE_TTL", configKey: "cache.ttl", transform: parseEnvNumber },
|
|
360
457
|
{ envVar: "LOG_LEVEL", configKey: "logger.level" },
|
|
361
|
-
{
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
458
|
+
{
|
|
459
|
+
envVar: "LOG_PRETTY",
|
|
460
|
+
configKey: "logger.pretty",
|
|
461
|
+
transform: parseEnvBoolean,
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
envVar: "HEALTH_ENABLED",
|
|
465
|
+
configKey: "health.enabled",
|
|
466
|
+
transform: parseEnvBoolean,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
envVar: "METRICS_ENABLED",
|
|
470
|
+
configKey: "metrics.enabled",
|
|
471
|
+
transform: parseEnvBoolean,
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
envVar: "TELEMETRY_ENABLED",
|
|
475
|
+
configKey: "telemetry.enabled",
|
|
476
|
+
transform: parseEnvBoolean,
|
|
477
|
+
},
|
|
365
478
|
{ envVar: "TELEMETRY_SERVICE_NAME", configKey: "telemetry.serviceName" },
|
|
366
479
|
{ envVar: "TELEMETRY_ENDPOINT", configKey: "telemetry.endpoint" },
|
|
367
|
-
{
|
|
368
|
-
|
|
369
|
-
|
|
480
|
+
{
|
|
481
|
+
envVar: "FRONTEND_DEV_SERVER",
|
|
482
|
+
configKey: "frontend.devServer",
|
|
483
|
+
transform: parseEnvBoolean,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
envVar: "FRONTEND_HMR",
|
|
487
|
+
configKey: "frontend.hmr",
|
|
488
|
+
transform: parseEnvBoolean,
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
envVar: "FRONTEND_PORT",
|
|
492
|
+
configKey: "frontend.port",
|
|
493
|
+
transform: parseEnvNumber,
|
|
494
|
+
},
|
|
370
495
|
];
|
|
371
496
|
|
|
372
497
|
/**
|
|
@@ -396,7 +521,10 @@ export function getEnvConfig(
|
|
|
396
521
|
/**
|
|
397
522
|
* Get an environment variable value
|
|
398
523
|
*/
|
|
399
|
-
export function getEnvValue(
|
|
524
|
+
export function getEnvValue(
|
|
525
|
+
key: string,
|
|
526
|
+
defaultValue?: string,
|
|
527
|
+
): string | undefined {
|
|
400
528
|
return Bun.env[key] ?? defaultValue;
|
|
401
529
|
}
|
|
402
530
|
|
|
@@ -405,4 +533,4 @@ export function getEnvValue(key: string, defaultValue?: string): string | undefi
|
|
|
405
533
|
*/
|
|
406
534
|
export function setEnvValue(key: string, value: string): void {
|
|
407
535
|
Bun.env[key] = value;
|
|
408
|
-
}
|
|
536
|
+
}
|