create-fluxstack 1.0.1 → 1.0.2
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/create-fluxstack.ts +2 -3
- package/package.json +1 -1
- package/.env +0 -30
- package/LICENSE +0 -21
- package/app/client/README.md +0 -69
- package/app/client/frontend-only.ts +0 -12
- package/app/client/index.html +0 -13
- package/app/client/public/vite.svg +0 -1
- package/app/client/src/App.css +0 -883
- package/app/client/src/App.tsx +0 -669
- package/app/client/src/assets/react.svg +0 -1
- package/app/client/src/components/TestPage.tsx +0 -453
- package/app/client/src/index.css +0 -51
- package/app/client/src/lib/eden-api.ts +0 -110
- package/app/client/src/main.tsx +0 -10
- package/app/client/src/vite-env.d.ts +0 -1
- package/app/client/tsconfig.app.json +0 -43
- package/app/client/tsconfig.json +0 -7
- package/app/client/tsconfig.node.json +0 -25
- package/app/server/app.ts +0 -10
- package/app/server/backend-only.ts +0 -15
- package/app/server/controllers/users.controller.ts +0 -69
- package/app/server/index.ts +0 -104
- package/app/server/routes/index.ts +0 -25
- package/app/server/routes/users.routes.ts +0 -121
- package/app/server/types/index.ts +0 -1
- package/app/shared/types/index.ts +0 -18
- package/bun.lock +0 -1053
- package/core/__tests__/integration.test.ts +0 -227
- package/core/build/index.ts +0 -186
- package/core/cli/command-registry.ts +0 -334
- package/core/cli/index.ts +0 -394
- package/core/cli/plugin-discovery.ts +0 -200
- package/core/client/standalone.ts +0 -57
- package/core/config/__tests__/config-loader.test.ts +0 -591
- package/core/config/__tests__/config-merger.test.ts +0 -657
- package/core/config/__tests__/env-converter.test.ts +0 -372
- package/core/config/__tests__/env-processor.test.ts +0 -431
- package/core/config/__tests__/env.test.ts +0 -452
- package/core/config/__tests__/integration.test.ts +0 -418
- package/core/config/__tests__/loader.test.ts +0 -331
- package/core/config/__tests__/schema.test.ts +0 -129
- package/core/config/__tests__/validator.test.ts +0 -318
- package/core/config/env-dynamic.ts +0 -326
- package/core/config/env.ts +0 -597
- package/core/config/index.ts +0 -317
- package/core/config/loader.ts +0 -546
- package/core/config/runtime-config.ts +0 -322
- package/core/config/schema.ts +0 -694
- package/core/config/validator.ts +0 -540
- package/core/framework/__tests__/server.test.ts +0 -233
- package/core/framework/client.ts +0 -132
- package/core/framework/index.ts +0 -8
- package/core/framework/server.ts +0 -501
- package/core/framework/types.ts +0 -63
- package/core/plugins/__tests__/built-in.test.ts.disabled +0 -366
- package/core/plugins/__tests__/manager.test.ts +0 -398
- package/core/plugins/__tests__/monitoring.test.ts +0 -401
- package/core/plugins/__tests__/registry.test.ts +0 -335
- package/core/plugins/built-in/index.ts +0 -142
- package/core/plugins/built-in/logger/index.ts +0 -180
- package/core/plugins/built-in/monitoring/README.md +0 -193
- package/core/plugins/built-in/monitoring/index.ts +0 -912
- package/core/plugins/built-in/static/index.ts +0 -289
- package/core/plugins/built-in/swagger/index.ts +0 -229
- package/core/plugins/built-in/vite/index.ts +0 -316
- package/core/plugins/config.ts +0 -348
- package/core/plugins/discovery.ts +0 -350
- package/core/plugins/executor.ts +0 -351
- package/core/plugins/index.ts +0 -195
- package/core/plugins/manager.ts +0 -583
- package/core/plugins/registry.ts +0 -424
- package/core/plugins/types.ts +0 -254
- package/core/server/framework.ts +0 -123
- package/core/server/index.ts +0 -8
- package/core/server/plugins/database.ts +0 -182
- package/core/server/plugins/logger.ts +0 -47
- package/core/server/plugins/swagger.ts +0 -34
- package/core/server/standalone.ts +0 -91
- package/core/templates/create-project.ts +0 -455
- package/core/types/api.ts +0 -169
- package/core/types/build.ts +0 -174
- package/core/types/config.ts +0 -68
- package/core/types/index.ts +0 -127
- package/core/types/plugin.ts +0 -94
- package/core/utils/__tests__/errors.test.ts +0 -139
- package/core/utils/__tests__/helpers.test.ts +0 -297
- package/core/utils/__tests__/logger.test.ts +0 -141
- package/core/utils/env-runtime-v2.ts +0 -232
- package/core/utils/env-runtime.ts +0 -252
- package/core/utils/errors/codes.ts +0 -115
- package/core/utils/errors/handlers.ts +0 -63
- package/core/utils/errors/index.ts +0 -81
- package/core/utils/helpers.ts +0 -180
- package/core/utils/index.ts +0 -18
- package/core/utils/logger/index.ts +0 -161
- package/core/utils/logger.ts +0 -106
- package/core/utils/monitoring/index.ts +0 -212
- package/tsconfig.json +0 -51
- package/vite.config.ts +0 -42
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Helper Utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
6
|
-
import {
|
|
7
|
-
formatBytes,
|
|
8
|
-
createTimer,
|
|
9
|
-
delay,
|
|
10
|
-
retry,
|
|
11
|
-
debounce,
|
|
12
|
-
throttle,
|
|
13
|
-
isProduction,
|
|
14
|
-
isDevelopment,
|
|
15
|
-
isTest,
|
|
16
|
-
deepMerge,
|
|
17
|
-
pick,
|
|
18
|
-
omit,
|
|
19
|
-
generateId,
|
|
20
|
-
safeJsonParse,
|
|
21
|
-
safeJsonStringify
|
|
22
|
-
} from '../helpers'
|
|
23
|
-
|
|
24
|
-
describe('Helper Utilities', () => {
|
|
25
|
-
describe('formatBytes', () => {
|
|
26
|
-
it('should format bytes correctly', () => {
|
|
27
|
-
expect(formatBytes(0)).toBe('0 Bytes')
|
|
28
|
-
expect(formatBytes(1024)).toBe('1 KB')
|
|
29
|
-
expect(formatBytes(1048576)).toBe('1 MB')
|
|
30
|
-
expect(formatBytes(1073741824)).toBe('1 GB')
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('should handle decimal places', () => {
|
|
34
|
-
expect(formatBytes(1536, 1)).toBe('1.5 KB')
|
|
35
|
-
expect(formatBytes(1536, 0)).toBe('2 KB')
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('createTimer', () => {
|
|
40
|
-
it('should measure time correctly', async () => {
|
|
41
|
-
const timer = createTimer('test')
|
|
42
|
-
await delay(10)
|
|
43
|
-
const duration = timer.end()
|
|
44
|
-
|
|
45
|
-
// Be more lenient in CI environments where timing can be variable
|
|
46
|
-
expect(duration).toBeGreaterThanOrEqual(5)
|
|
47
|
-
expect(timer.label).toBe('test')
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
describe('delay', () => {
|
|
52
|
-
it('should delay execution', async () => {
|
|
53
|
-
const start = Date.now()
|
|
54
|
-
await delay(50)
|
|
55
|
-
const end = Date.now()
|
|
56
|
-
|
|
57
|
-
// Be more lenient with timing in CI environments - allow for 10ms variance
|
|
58
|
-
expect(end - start).toBeGreaterThanOrEqual(40)
|
|
59
|
-
expect(end - start).toBeLessThan(200) // Ensure it's not too long
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe('retry', () => {
|
|
64
|
-
it('should succeed on first attempt', async () => {
|
|
65
|
-
const fn = vi.fn().mockResolvedValue('success')
|
|
66
|
-
const result = await retry(fn, 3, 10)
|
|
67
|
-
|
|
68
|
-
expect(result).toBe('success')
|
|
69
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('should retry on failure and eventually succeed', async () => {
|
|
73
|
-
const fn = vi.fn()
|
|
74
|
-
.mockRejectedValueOnce(new Error('fail 1'))
|
|
75
|
-
.mockRejectedValueOnce(new Error('fail 2'))
|
|
76
|
-
.mockResolvedValue('success')
|
|
77
|
-
|
|
78
|
-
const result = await retry(fn, 3, 10)
|
|
79
|
-
|
|
80
|
-
expect(result).toBe('success')
|
|
81
|
-
expect(fn).toHaveBeenCalledTimes(3)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should throw after max attempts', async () => {
|
|
85
|
-
const fn = vi.fn().mockRejectedValue(new Error('always fails'))
|
|
86
|
-
|
|
87
|
-
await expect(retry(fn, 2, 10)).rejects.toThrow('always fails')
|
|
88
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe('debounce', () => {
|
|
93
|
-
beforeEach(() => {
|
|
94
|
-
vi.useFakeTimers()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
afterEach(() => {
|
|
98
|
-
vi.useRealTimers()
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('should debounce function calls', () => {
|
|
102
|
-
const fn = vi.fn()
|
|
103
|
-
const debouncedFn = debounce(fn, 100)
|
|
104
|
-
|
|
105
|
-
debouncedFn('arg1')
|
|
106
|
-
debouncedFn('arg2')
|
|
107
|
-
debouncedFn('arg3')
|
|
108
|
-
|
|
109
|
-
expect(fn).not.toHaveBeenCalled()
|
|
110
|
-
|
|
111
|
-
vi.advanceTimersByTime(100)
|
|
112
|
-
|
|
113
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
114
|
-
expect(fn).toHaveBeenCalledWith('arg3')
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
describe('throttle', () => {
|
|
119
|
-
beforeEach(() => {
|
|
120
|
-
vi.useFakeTimers()
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
afterEach(() => {
|
|
124
|
-
vi.useRealTimers()
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('should throttle function calls', () => {
|
|
128
|
-
const fn = vi.fn()
|
|
129
|
-
const throttledFn = throttle(fn, 100)
|
|
130
|
-
|
|
131
|
-
throttledFn('arg1')
|
|
132
|
-
throttledFn('arg2')
|
|
133
|
-
throttledFn('arg3')
|
|
134
|
-
|
|
135
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
136
|
-
expect(fn).toHaveBeenCalledWith('arg1')
|
|
137
|
-
|
|
138
|
-
vi.advanceTimersByTime(100)
|
|
139
|
-
|
|
140
|
-
throttledFn('arg4')
|
|
141
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
142
|
-
expect(fn).toHaveBeenCalledWith('arg4')
|
|
143
|
-
})
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
describe('Environment Checks', () => {
|
|
147
|
-
const originalEnv = process.env.NODE_ENV
|
|
148
|
-
|
|
149
|
-
afterEach(() => {
|
|
150
|
-
process.env.NODE_ENV = originalEnv
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('should detect production environment', () => {
|
|
154
|
-
process.env.NODE_ENV = 'production'
|
|
155
|
-
expect(isProduction()).toBe(true)
|
|
156
|
-
expect(isDevelopment()).toBe(false)
|
|
157
|
-
expect(isTest()).toBe(false)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('should detect development environment', () => {
|
|
161
|
-
process.env.NODE_ENV = 'development'
|
|
162
|
-
expect(isProduction()).toBe(false)
|
|
163
|
-
expect(isDevelopment()).toBe(true)
|
|
164
|
-
expect(isTest()).toBe(false)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('should detect test environment', () => {
|
|
168
|
-
process.env.NODE_ENV = 'test'
|
|
169
|
-
expect(isProduction()).toBe(false)
|
|
170
|
-
expect(isDevelopment()).toBe(false)
|
|
171
|
-
expect(isTest()).toBe(true)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('should default to development when NODE_ENV is not set', () => {
|
|
175
|
-
delete process.env.NODE_ENV
|
|
176
|
-
expect(isDevelopment()).toBe(true)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
describe('Object Utilities', () => {
|
|
181
|
-
describe('deepMerge', () => {
|
|
182
|
-
it('should merge objects deeply', () => {
|
|
183
|
-
const target = {
|
|
184
|
-
a: 1,
|
|
185
|
-
b: {
|
|
186
|
-
c: 2,
|
|
187
|
-
d: 3
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const source = {
|
|
192
|
-
b: {
|
|
193
|
-
c: 2, // Keep existing property
|
|
194
|
-
d: 4,
|
|
195
|
-
e: 5
|
|
196
|
-
},
|
|
197
|
-
f: 6
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const result = deepMerge(target, source)
|
|
201
|
-
|
|
202
|
-
expect(result).toEqual({
|
|
203
|
-
a: 1,
|
|
204
|
-
b: {
|
|
205
|
-
c: 2,
|
|
206
|
-
d: 4,
|
|
207
|
-
e: 5
|
|
208
|
-
},
|
|
209
|
-
f: 6
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('should handle arrays correctly', () => {
|
|
214
|
-
const target = { arr: [1, 2, 3] }
|
|
215
|
-
const source = { arr: [4, 5, 6] }
|
|
216
|
-
|
|
217
|
-
const result = deepMerge(target, source)
|
|
218
|
-
|
|
219
|
-
expect(result.arr).toEqual([4, 5, 6])
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
describe('pick', () => {
|
|
224
|
-
it('should pick specified keys', () => {
|
|
225
|
-
const obj = { a: 1, b: 2, c: 3, d: 4 }
|
|
226
|
-
const result = pick(obj, ['a', 'c'])
|
|
227
|
-
|
|
228
|
-
expect(result).toEqual({ a: 1, c: 3 })
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
it('should handle non-existent keys', () => {
|
|
232
|
-
const obj = { a: 1, b: 2 }
|
|
233
|
-
const result = pick(obj, ['a', 'c'] as any)
|
|
234
|
-
|
|
235
|
-
expect(result).toEqual({ a: 1 })
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
describe('omit', () => {
|
|
240
|
-
it('should omit specified keys', () => {
|
|
241
|
-
const obj = { a: 1, b: 2, c: 3, d: 4 }
|
|
242
|
-
const result = omit(obj, ['b', 'd'])
|
|
243
|
-
|
|
244
|
-
expect(result).toEqual({ a: 1, c: 3 })
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
describe('String Utilities', () => {
|
|
250
|
-
describe('generateId', () => {
|
|
251
|
-
it('should generate id with default length', () => {
|
|
252
|
-
const id = generateId()
|
|
253
|
-
expect(id).toHaveLength(8)
|
|
254
|
-
expect(id).toMatch(/^[A-Za-z0-9]+$/)
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('should generate id with custom length', () => {
|
|
258
|
-
const id = generateId(16)
|
|
259
|
-
expect(id).toHaveLength(16)
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('should generate unique ids', () => {
|
|
263
|
-
const id1 = generateId()
|
|
264
|
-
const id2 = generateId()
|
|
265
|
-
expect(id1).not.toBe(id2)
|
|
266
|
-
})
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
describe('safeJsonParse', () => {
|
|
270
|
-
it('should parse valid JSON', () => {
|
|
271
|
-
const result = safeJsonParse('{"a": 1}', {})
|
|
272
|
-
expect(result).toEqual({ a: 1 })
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
it('should return fallback for invalid JSON', () => {
|
|
276
|
-
const fallback = { error: true }
|
|
277
|
-
const result = safeJsonParse('invalid json', fallback)
|
|
278
|
-
expect(result).toBe(fallback)
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
describe('safeJsonStringify', () => {
|
|
283
|
-
it('should stringify valid objects', () => {
|
|
284
|
-
const result = safeJsonStringify({ a: 1 })
|
|
285
|
-
expect(result).toBe('{"a":1}')
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
it('should return fallback for circular references', () => {
|
|
289
|
-
const circular: any = { a: 1 }
|
|
290
|
-
circular.self = circular
|
|
291
|
-
|
|
292
|
-
const result = safeJsonStringify(circular, '{"error": true}')
|
|
293
|
-
expect(result).toBe('{"error": true}')
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
})
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Logger Utility
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
6
|
-
|
|
7
|
-
// Set test environment
|
|
8
|
-
process.env.NODE_ENV = 'test'
|
|
9
|
-
|
|
10
|
-
// Import the logger
|
|
11
|
-
import { logger as realLogger, log as realLog } from '../logger'
|
|
12
|
-
|
|
13
|
-
describe('Logger', () => {
|
|
14
|
-
let consoleSpy: {
|
|
15
|
-
debug: any
|
|
16
|
-
info: any
|
|
17
|
-
warn: any
|
|
18
|
-
error: any
|
|
19
|
-
}
|
|
20
|
-
let logger: typeof realLogger
|
|
21
|
-
let log: typeof realLog
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
consoleSpy = {
|
|
25
|
-
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
|
|
26
|
-
info: vi.spyOn(console, 'info').mockImplementation(() => {}),
|
|
27
|
-
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
|
|
28
|
-
error: vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
29
|
-
}
|
|
30
|
-
logger = realLogger
|
|
31
|
-
log = realLog
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
vi.restoreAllMocks()
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
describe('Log Levels', () => {
|
|
39
|
-
it('should log info messages', () => {
|
|
40
|
-
logger.info('Test info message')
|
|
41
|
-
expect(consoleSpy.info).toHaveBeenCalled()
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should log warn messages', () => {
|
|
45
|
-
logger.warn('Test warn message')
|
|
46
|
-
expect(consoleSpy.warn).toHaveBeenCalled()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should log error messages', () => {
|
|
50
|
-
logger.error('Test error message')
|
|
51
|
-
expect(consoleSpy.error).toHaveBeenCalled()
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('should not log debug messages when log level is info', () => {
|
|
55
|
-
logger.debug('Test debug message')
|
|
56
|
-
expect(consoleSpy.debug).not.toHaveBeenCalled()
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('Message Formatting', () => {
|
|
61
|
-
it('should format messages with timestamp and level', () => {
|
|
62
|
-
logger.info('Test message')
|
|
63
|
-
|
|
64
|
-
const call = consoleSpy.info.mock.calls[0][0]
|
|
65
|
-
expect(call).toMatch(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] INFO Test message/)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('should include metadata in log messages', () => {
|
|
69
|
-
const metadata = { userId: 123, action: 'login' }
|
|
70
|
-
logger.info('User action', metadata)
|
|
71
|
-
|
|
72
|
-
const call = consoleSpy.info.mock.calls[0][0]
|
|
73
|
-
expect(call).toContain(JSON.stringify(metadata))
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('Contextual Logging', () => {
|
|
78
|
-
it('should support contextual logging (basic test)', () => {
|
|
79
|
-
// Test that logger has basic functionality
|
|
80
|
-
expect(logger).toBeDefined()
|
|
81
|
-
expect(typeof logger.info).toBe('function')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('should have log convenience object', () => {
|
|
85
|
-
// Test that log convenience object exists
|
|
86
|
-
expect(log).toBeDefined()
|
|
87
|
-
expect(typeof log.info).toBe('function')
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('Performance Logging', () => {
|
|
92
|
-
it('should support basic logging functionality', () => {
|
|
93
|
-
// Test basic functionality without advanced features
|
|
94
|
-
expect(logger).toBeDefined()
|
|
95
|
-
expect(typeof logger.info).toBe('function')
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('should handle logging without errors', () => {
|
|
99
|
-
// Basic test without expecting specific console output
|
|
100
|
-
expect(() => {
|
|
101
|
-
logger.info('Test message')
|
|
102
|
-
log.info('Test message via convenience function')
|
|
103
|
-
}).not.toThrow()
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('HTTP Request Logging', () => {
|
|
108
|
-
it('should log HTTP requests', () => {
|
|
109
|
-
logger.request('GET', '/api/users', 200, 150)
|
|
110
|
-
|
|
111
|
-
expect(consoleSpy.info).toHaveBeenCalledWith(
|
|
112
|
-
expect.stringMatching(/GET \/api\/users 200 \(150ms\)/)
|
|
113
|
-
)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('should log requests without status and duration', () => {
|
|
117
|
-
logger.request('POST', '/api/users')
|
|
118
|
-
|
|
119
|
-
expect(consoleSpy.info).toHaveBeenCalledWith(
|
|
120
|
-
expect.stringMatching(/POST \/api\/users/)
|
|
121
|
-
)
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
describe('Convenience Functions', () => {
|
|
126
|
-
it('should provide log convenience functions', () => {
|
|
127
|
-
log.info('Test message')
|
|
128
|
-
expect(consoleSpy.info).toHaveBeenCalled()
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('should provide plugin logging', () => {
|
|
132
|
-
log.plugin('test-plugin', 'Plugin message')
|
|
133
|
-
expect(consoleSpy.debug).not.toHaveBeenCalled() // debug level, won't show with info level
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('should provide framework logging', () => {
|
|
137
|
-
log.framework('Framework message')
|
|
138
|
-
expect(consoleSpy.info).toHaveBeenCalled()
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
})
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime Environment Loader V2 - Simplified API
|
|
3
|
-
* Mais elegante com casting automático e acesso direto
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Enhanced environment variable loader with smart casting
|
|
8
|
-
*/
|
|
9
|
-
class SmartEnvLoader {
|
|
10
|
-
private envAccessor: () => Record<string, string | undefined>
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
this.envAccessor = this.createDynamicAccessor()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private createDynamicAccessor(): () => Record<string, string | undefined> {
|
|
17
|
-
const globalScope = globalThis as any
|
|
18
|
-
|
|
19
|
-
return () => {
|
|
20
|
-
// Try Bun.env first (most reliable in Bun runtime)
|
|
21
|
-
if (globalScope['Bun'] && globalScope['Bun']['env']) {
|
|
22
|
-
return globalScope['Bun']['env']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Fallback to process.env with dynamic access
|
|
26
|
-
if (globalScope['process'] && globalScope['process']['env']) {
|
|
27
|
-
return globalScope['process']['env']
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Final fallback
|
|
31
|
-
const proc = eval('typeof process !== "undefined" ? process : null')
|
|
32
|
-
return proc?.env || {}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Smart get with automatic type conversion based on default value
|
|
38
|
-
*/
|
|
39
|
-
get<T>(key: string, defaultValue?: T): T {
|
|
40
|
-
const env = this.envAccessor()
|
|
41
|
-
const value = env[key]
|
|
42
|
-
|
|
43
|
-
if (!value || value === '') {
|
|
44
|
-
return defaultValue as T
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Auto-detect type from default value
|
|
48
|
-
if (typeof defaultValue === 'number') {
|
|
49
|
-
const parsed = parseInt(value, 10)
|
|
50
|
-
return (isNaN(parsed) ? defaultValue : parsed) as T
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (typeof defaultValue === 'boolean') {
|
|
54
|
-
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase()) as T
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (Array.isArray(defaultValue)) {
|
|
58
|
-
return value.split(',').map(v => v.trim()).filter(Boolean) as T
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (typeof defaultValue === 'object' && defaultValue !== null) {
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(value) as T
|
|
64
|
-
} catch {
|
|
65
|
-
return defaultValue
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return value as T
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check if environment variable exists
|
|
74
|
-
*/
|
|
75
|
-
has(key: string): boolean {
|
|
76
|
-
const env = this.envAccessor()
|
|
77
|
-
return key in env && env[key] !== undefined && env[key] !== ''
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get all environment variables
|
|
82
|
-
*/
|
|
83
|
-
all(): Record<string, string> {
|
|
84
|
-
const env = this.envAccessor()
|
|
85
|
-
const result: Record<string, string> = {}
|
|
86
|
-
|
|
87
|
-
for (const [key, value] of Object.entries(env)) {
|
|
88
|
-
if (value !== undefined && value !== '') {
|
|
89
|
-
result[key] = value
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return result
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Create singleton instance
|
|
98
|
-
const smartEnv = new SmartEnvLoader()
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Simplified env API with smart casting
|
|
102
|
-
*/
|
|
103
|
-
export const env = {
|
|
104
|
-
/**
|
|
105
|
-
* Smart get - automatically casts based on default value type
|
|
106
|
-
* Usage:
|
|
107
|
-
* env.get('PORT', 3000) -> number
|
|
108
|
-
* env.get('DEBUG', false) -> boolean
|
|
109
|
-
* env.get('ORIGINS', ['*']) -> string[]
|
|
110
|
-
* env.get('HOST', 'localhost') -> string
|
|
111
|
-
*/
|
|
112
|
-
get: <T>(key: string, defaultValue?: T): T => smartEnv.get(key, defaultValue),
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if env var exists
|
|
116
|
-
*/
|
|
117
|
-
has: (key: string) => smartEnv.has(key),
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get all env vars
|
|
121
|
-
*/
|
|
122
|
-
all: () => smartEnv.all(),
|
|
123
|
-
|
|
124
|
-
// Common environment variables as properties with smart defaults
|
|
125
|
-
get NODE_ENV() { return this.get('NODE_ENV', 'development') },
|
|
126
|
-
get PORT() { return this.get('PORT', 3000) },
|
|
127
|
-
get HOST() { return this.get('HOST', 'localhost') },
|
|
128
|
-
get DEBUG() { return this.get('DEBUG', false) },
|
|
129
|
-
get LOG_LEVEL() { return this.get('LOG_LEVEL', 'info') },
|
|
130
|
-
get DATABASE_URL() { return this.get('DATABASE_URL', '') },
|
|
131
|
-
get JWT_SECRET() { return this.get('JWT_SECRET', '') },
|
|
132
|
-
get CORS_ORIGINS() { return this.get('CORS_ORIGINS', ['*']) },
|
|
133
|
-
get VITE_PORT() { return this.get('VITE_PORT', 5173) },
|
|
134
|
-
get API_PREFIX() { return this.get('API_PREFIX', '/api') },
|
|
135
|
-
|
|
136
|
-
// App specific
|
|
137
|
-
get FLUXSTACK_APP_NAME() { return this.get('FLUXSTACK_APP_NAME', 'FluxStack') },
|
|
138
|
-
get FLUXSTACK_APP_VERSION() { return this.get('FLUXSTACK_APP_VERSION', '1.0.0') },
|
|
139
|
-
|
|
140
|
-
// Monitoring
|
|
141
|
-
get ENABLE_MONITORING() { return this.get('ENABLE_MONITORING', false) },
|
|
142
|
-
get ENABLE_SWAGGER() { return this.get('ENABLE_SWAGGER', true) },
|
|
143
|
-
get ENABLE_METRICS() { return this.get('ENABLE_METRICS', false) },
|
|
144
|
-
|
|
145
|
-
// Database
|
|
146
|
-
get DB_HOST() { return this.get('DB_HOST', 'localhost') },
|
|
147
|
-
get DB_PORT() { return this.get('DB_PORT', 5432) },
|
|
148
|
-
get DB_NAME() { return this.get('DB_NAME', '') },
|
|
149
|
-
get DB_USER() { return this.get('DB_USER', '') },
|
|
150
|
-
get DB_PASSWORD() { return this.get('DB_PASSWORD', '') },
|
|
151
|
-
get DB_SSL() { return this.get('DB_SSL', false) },
|
|
152
|
-
|
|
153
|
-
// SMTP
|
|
154
|
-
get SMTP_HOST() { return this.get('SMTP_HOST', '') },
|
|
155
|
-
get SMTP_PORT() { return this.get('SMTP_PORT', 587) },
|
|
156
|
-
get SMTP_USER() { return this.get('SMTP_USER', '') },
|
|
157
|
-
get SMTP_PASSWORD() { return this.get('SMTP_PASSWORD', '') },
|
|
158
|
-
get SMTP_SECURE() { return this.get('SMTP_SECURE', false) }
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Create namespaced environment access
|
|
163
|
-
* Usage: const db = createNamespace('DATABASE_')
|
|
164
|
-
* db.get('URL') -> reads DATABASE_URL
|
|
165
|
-
*/
|
|
166
|
-
export function createNamespace(prefix: string) {
|
|
167
|
-
return {
|
|
168
|
-
get: <T>(key: string, defaultValue?: T): T =>
|
|
169
|
-
smartEnv.get(`${prefix}${key}`, defaultValue),
|
|
170
|
-
|
|
171
|
-
has: (key: string) => smartEnv.has(`${prefix}${key}`),
|
|
172
|
-
|
|
173
|
-
all: () => {
|
|
174
|
-
const allEnv = smartEnv.all()
|
|
175
|
-
const namespaced: Record<string, string> = {}
|
|
176
|
-
|
|
177
|
-
for (const [key, value] of Object.entries(allEnv)) {
|
|
178
|
-
if (key.startsWith(prefix)) {
|
|
179
|
-
namespaced[key.slice(prefix.length)] = value
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return namespaced
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Environment validation
|
|
190
|
-
*/
|
|
191
|
-
export const validate = {
|
|
192
|
-
require(keys: string[]): void {
|
|
193
|
-
const missing = keys.filter(key => !smartEnv.has(key))
|
|
194
|
-
if (missing.length > 0) {
|
|
195
|
-
throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
oneOf(key: string, validValues: string[]): void {
|
|
200
|
-
const value = smartEnv.get(key, '')
|
|
201
|
-
if (value && !validValues.includes(value)) {
|
|
202
|
-
throw new Error(`${key} must be one of: ${validValues.join(', ')}, got: ${value}`)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Convenience functions
|
|
209
|
-
*/
|
|
210
|
-
export const helpers = {
|
|
211
|
-
isDevelopment: () => env.NODE_ENV === 'development',
|
|
212
|
-
isProduction: () => env.NODE_ENV === 'production',
|
|
213
|
-
isTest: () => env.NODE_ENV === 'test',
|
|
214
|
-
|
|
215
|
-
getDatabaseUrl: () => {
|
|
216
|
-
const url = env.DATABASE_URL
|
|
217
|
-
if (url) return url
|
|
218
|
-
|
|
219
|
-
const { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD } = env
|
|
220
|
-
if (DB_HOST && DB_NAME) {
|
|
221
|
-
const auth = DB_USER ? `${DB_USER}:${DB_PASSWORD}@` : ''
|
|
222
|
-
return `postgres://${auth}${DB_HOST}:${DB_PORT}/${DB_NAME}`
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return null
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
getServerUrl: () => `http://${env.HOST}:${env.PORT}`,
|
|
229
|
-
getClientUrl: () => `http://${env.HOST}:${env.VITE_PORT}`
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export default env
|