create-fluxstack 1.0.0
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/.env +30 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/app/client/README.md +69 -0
- package/app/client/frontend-only.ts +12 -0
- package/app/client/index.html +13 -0
- package/app/client/public/vite.svg +1 -0
- package/app/client/src/App.css +883 -0
- package/app/client/src/App.tsx +669 -0
- package/app/client/src/assets/react.svg +1 -0
- package/app/client/src/components/TestPage.tsx +453 -0
- package/app/client/src/index.css +51 -0
- package/app/client/src/lib/eden-api.ts +110 -0
- package/app/client/src/main.tsx +10 -0
- package/app/client/src/vite-env.d.ts +1 -0
- package/app/client/tsconfig.app.json +43 -0
- package/app/client/tsconfig.json +7 -0
- package/app/client/tsconfig.node.json +25 -0
- package/app/server/app.ts +10 -0
- package/app/server/backend-only.ts +15 -0
- package/app/server/controllers/users.controller.ts +69 -0
- package/app/server/index.ts +104 -0
- package/app/server/routes/index.ts +25 -0
- package/app/server/routes/users.routes.ts +121 -0
- package/app/server/types/index.ts +1 -0
- package/app/shared/types/index.ts +18 -0
- package/bun.lock +1053 -0
- package/core/__tests__/integration.test.ts +227 -0
- package/core/build/index.ts +186 -0
- package/core/cli/command-registry.ts +334 -0
- package/core/cli/index.ts +394 -0
- package/core/cli/plugin-discovery.ts +200 -0
- package/core/client/standalone.ts +57 -0
- package/core/config/__tests__/config-loader.test.ts +591 -0
- package/core/config/__tests__/config-merger.test.ts +657 -0
- package/core/config/__tests__/env-converter.test.ts +372 -0
- package/core/config/__tests__/env-processor.test.ts +431 -0
- package/core/config/__tests__/env.test.ts +452 -0
- package/core/config/__tests__/integration.test.ts +418 -0
- package/core/config/__tests__/loader.test.ts +331 -0
- package/core/config/__tests__/schema.test.ts +129 -0
- package/core/config/__tests__/validator.test.ts +318 -0
- package/core/config/env-dynamic.ts +326 -0
- package/core/config/env.ts +597 -0
- package/core/config/index.ts +317 -0
- package/core/config/loader.ts +546 -0
- package/core/config/runtime-config.ts +322 -0
- package/core/config/schema.ts +694 -0
- package/core/config/validator.ts +540 -0
- package/core/framework/__tests__/server.test.ts +233 -0
- package/core/framework/client.ts +132 -0
- package/core/framework/index.ts +8 -0
- package/core/framework/server.ts +501 -0
- package/core/framework/types.ts +63 -0
- package/core/plugins/__tests__/built-in.test.ts.disabled +366 -0
- package/core/plugins/__tests__/manager.test.ts +398 -0
- package/core/plugins/__tests__/monitoring.test.ts +401 -0
- package/core/plugins/__tests__/registry.test.ts +335 -0
- package/core/plugins/built-in/index.ts +142 -0
- package/core/plugins/built-in/logger/index.ts +180 -0
- package/core/plugins/built-in/monitoring/README.md +193 -0
- package/core/plugins/built-in/monitoring/index.ts +912 -0
- package/core/plugins/built-in/static/index.ts +289 -0
- package/core/plugins/built-in/swagger/index.ts +229 -0
- package/core/plugins/built-in/vite/index.ts +316 -0
- package/core/plugins/config.ts +348 -0
- package/core/plugins/discovery.ts +350 -0
- package/core/plugins/executor.ts +351 -0
- package/core/plugins/index.ts +195 -0
- package/core/plugins/manager.ts +583 -0
- package/core/plugins/registry.ts +424 -0
- package/core/plugins/types.ts +254 -0
- package/core/server/framework.ts +123 -0
- package/core/server/index.ts +8 -0
- package/core/server/plugins/database.ts +182 -0
- package/core/server/plugins/logger.ts +47 -0
- package/core/server/plugins/swagger.ts +34 -0
- package/core/server/standalone.ts +91 -0
- package/core/templates/create-project.ts +455 -0
- package/core/types/api.ts +169 -0
- package/core/types/build.ts +174 -0
- package/core/types/config.ts +68 -0
- package/core/types/index.ts +127 -0
- package/core/types/plugin.ts +94 -0
- package/core/utils/__tests__/errors.test.ts +139 -0
- package/core/utils/__tests__/helpers.test.ts +297 -0
- package/core/utils/__tests__/logger.test.ts +141 -0
- package/core/utils/env-runtime-v2.ts +232 -0
- package/core/utils/env-runtime.ts +252 -0
- package/core/utils/errors/codes.ts +115 -0
- package/core/utils/errors/handlers.ts +63 -0
- package/core/utils/errors/index.ts +81 -0
- package/core/utils/helpers.ts +180 -0
- package/core/utils/index.ts +18 -0
- package/core/utils/logger/index.ts +161 -0
- package/core/utils/logger.ts +106 -0
- package/core/utils/monitoring/index.ts +212 -0
- package/create-fluxstack.ts +231 -0
- package/package.json +43 -0
- package/tsconfig.json +51 -0
- package/vite.config.ts +42 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Logger
|
|
3
|
+
* Environment-aware logging system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Environment info is handled via process.env directly
|
|
7
|
+
|
|
8
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
9
|
+
|
|
10
|
+
export interface Logger {
|
|
11
|
+
debug(message: string, meta?: any): void
|
|
12
|
+
info(message: string, meta?: any): void
|
|
13
|
+
warn(message: string, meta?: any): void
|
|
14
|
+
error(message: string, meta?: any): void
|
|
15
|
+
|
|
16
|
+
// Contextual logging
|
|
17
|
+
child(context: any): Logger
|
|
18
|
+
|
|
19
|
+
// Performance logging
|
|
20
|
+
time(label: string): void
|
|
21
|
+
timeEnd(label: string): void
|
|
22
|
+
|
|
23
|
+
// Request logging
|
|
24
|
+
request(method: string, path: string, status?: number, duration?: number): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class FluxStackLogger implements Logger {
|
|
28
|
+
private static instance: FluxStackLogger | null = null
|
|
29
|
+
private logLevel: LogLevel
|
|
30
|
+
private context: any = {}
|
|
31
|
+
private timers: Map<string, number> = new Map()
|
|
32
|
+
|
|
33
|
+
private constructor(context?: any) {
|
|
34
|
+
// Default to 'info' level, can be overridden by config
|
|
35
|
+
this.logLevel = (process.env.LOG_LEVEL as LogLevel) || 'info'
|
|
36
|
+
this.context = context || {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static getInstance(): FluxStackLogger {
|
|
40
|
+
if (FluxStackLogger.instance === null) {
|
|
41
|
+
FluxStackLogger.instance = new FluxStackLogger()
|
|
42
|
+
}
|
|
43
|
+
return FluxStackLogger.instance
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private shouldLog(level: LogLevel): boolean {
|
|
47
|
+
const levels: Record<LogLevel, number> = {
|
|
48
|
+
debug: 0,
|
|
49
|
+
info: 1,
|
|
50
|
+
warn: 2,
|
|
51
|
+
error: 3
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return levels[level] >= levels[this.logLevel]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private formatMessage(level: LogLevel, message: string, meta?: any): string {
|
|
58
|
+
const timestamp = new Date().toISOString()
|
|
59
|
+
const levelStr = level.toUpperCase().padEnd(5)
|
|
60
|
+
|
|
61
|
+
let formatted = `[${timestamp}] ${levelStr}`
|
|
62
|
+
|
|
63
|
+
// Add context if available
|
|
64
|
+
if (Object.keys(this.context).length > 0) {
|
|
65
|
+
const contextStr = Object.entries(this.context)
|
|
66
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
67
|
+
.join(' ')
|
|
68
|
+
formatted += ` [${contextStr}]`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
formatted += ` ${message}`
|
|
72
|
+
|
|
73
|
+
if (meta && typeof meta === 'object') {
|
|
74
|
+
formatted += ` ${JSON.stringify(meta)}`
|
|
75
|
+
} else if (meta !== undefined) {
|
|
76
|
+
formatted += ` ${meta}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return formatted
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
debug(message: string, meta?: any): void {
|
|
83
|
+
if (this.shouldLog('debug')) {
|
|
84
|
+
console.debug(this.formatMessage('debug', message, meta))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
info(message: string, meta?: any): void {
|
|
89
|
+
if (this.shouldLog('info')) {
|
|
90
|
+
console.info(this.formatMessage('info', message, meta))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
warn(message: string, meta?: any): void {
|
|
95
|
+
if (this.shouldLog('warn')) {
|
|
96
|
+
console.warn(this.formatMessage('warn', message, meta))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
error(message: string, meta?: any): void {
|
|
101
|
+
if (this.shouldLog('error')) {
|
|
102
|
+
console.error(this.formatMessage('error', message, meta))
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Contextual logging
|
|
107
|
+
child(context: any): FluxStackLogger {
|
|
108
|
+
return new FluxStackLogger({ ...this.context, ...context })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Performance logging
|
|
112
|
+
time(label: string): void {
|
|
113
|
+
this.timers.set(label, Date.now())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
timeEnd(label: string): void {
|
|
117
|
+
const startTime = this.timers.get(label)
|
|
118
|
+
if (startTime) {
|
|
119
|
+
const duration = Date.now() - startTime
|
|
120
|
+
this.info(`Timer ${label}: ${duration}ms`)
|
|
121
|
+
this.timers.delete(label)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// HTTP request logging
|
|
126
|
+
request(method: string, path: string, status?: number, duration?: number): void {
|
|
127
|
+
const statusStr = status ? ` ${status}` : ''
|
|
128
|
+
const durationStr = duration ? ` (${duration}ms)` : ''
|
|
129
|
+
this.info(`${method} ${path}${statusStr}${durationStr}`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Plugin logging
|
|
133
|
+
plugin(pluginName: string, message: string, meta?: any): void {
|
|
134
|
+
this.debug(`[${pluginName}] ${message}`, meta)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Framework logging
|
|
138
|
+
framework(message: string, meta?: any): void {
|
|
139
|
+
this.info(`[FluxStack] ${message}`, meta)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Export singleton instance
|
|
144
|
+
export const logger = FluxStackLogger.getInstance()
|
|
145
|
+
|
|
146
|
+
// Export convenience functions
|
|
147
|
+
export const log = {
|
|
148
|
+
debug: (message: string, meta?: any) => logger.debug(message, meta),
|
|
149
|
+
info: (message: string, meta?: any) => logger.info(message, meta),
|
|
150
|
+
warn: (message: string, meta?: any) => logger.warn(message, meta),
|
|
151
|
+
error: (message: string, meta?: any) => logger.error(message, meta),
|
|
152
|
+
request: (method: string, path: string, status?: number, duration?: number) =>
|
|
153
|
+
logger.request(method, path, status, duration),
|
|
154
|
+
plugin: (pluginName: string, message: string, meta?: any) =>
|
|
155
|
+
logger.plugin(pluginName, message, meta),
|
|
156
|
+
framework: (message: string, meta?: any) =>
|
|
157
|
+
logger.framework(message, meta),
|
|
158
|
+
child: (context: any) => logger.child(context),
|
|
159
|
+
time: (label: string) => logger.time(label),
|
|
160
|
+
timeEnd: (label: string) => logger.timeEnd(label)
|
|
161
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Logger
|
|
3
|
+
* Environment-aware logging system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
|
|
7
|
+
|
|
8
|
+
class Logger {
|
|
9
|
+
private static instance: Logger | null = null
|
|
10
|
+
private logLevel: LogLevel
|
|
11
|
+
|
|
12
|
+
private constructor() {
|
|
13
|
+
this.logLevel = (process.env.LOG_LEVEL as LogLevel) || 'info'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static getInstance(): Logger {
|
|
17
|
+
if (Logger.instance === null) {
|
|
18
|
+
Logger.instance = new Logger()
|
|
19
|
+
}
|
|
20
|
+
return Logger.instance
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private shouldLog(level: LogLevel): boolean {
|
|
24
|
+
const levels: Record<LogLevel, number> = {
|
|
25
|
+
debug: 0,
|
|
26
|
+
info: 1,
|
|
27
|
+
warn: 2,
|
|
28
|
+
error: 3
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return levels[level] >= levels[this.logLevel]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private formatMessage(level: LogLevel, message: string, meta?: any): string {
|
|
35
|
+
const timestamp = new Date().toISOString()
|
|
36
|
+
const levelStr = level.toUpperCase().padEnd(5)
|
|
37
|
+
|
|
38
|
+
let formatted = `[${timestamp}] ${levelStr} ${message}`
|
|
39
|
+
|
|
40
|
+
if (meta && typeof meta === 'object') {
|
|
41
|
+
formatted += ` ${JSON.stringify(meta)}`
|
|
42
|
+
} else if (meta !== undefined) {
|
|
43
|
+
formatted += ` ${meta}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return formatted
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
debug(message: string, meta?: any): void {
|
|
50
|
+
if (this.shouldLog('debug')) {
|
|
51
|
+
console.debug(this.formatMessage('debug', message, meta))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
info(message: string, meta?: any): void {
|
|
56
|
+
if (this.shouldLog('info')) {
|
|
57
|
+
console.info(this.formatMessage('info', message, meta))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
warn(message: string, meta?: any): void {
|
|
62
|
+
if (this.shouldLog('warn')) {
|
|
63
|
+
console.warn(this.formatMessage('warn', message, meta))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
error(message: string, meta?: any): void {
|
|
68
|
+
if (this.shouldLog('error')) {
|
|
69
|
+
console.error(this.formatMessage('error', message, meta))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// HTTP request logging
|
|
74
|
+
request(method: string, path: string, status?: number, duration?: number): void {
|
|
75
|
+
const statusStr = status ? ` ${status}` : ''
|
|
76
|
+
const durationStr = duration ? ` (${duration}ms)` : ''
|
|
77
|
+
this.info(`${method} ${path}${statusStr}${durationStr}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Plugin logging
|
|
81
|
+
plugin(pluginName: string, message: string, meta?: any): void {
|
|
82
|
+
this.debug(`[${pluginName}] ${message}`, meta)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Framework logging
|
|
86
|
+
framework(message: string, meta?: any): void {
|
|
87
|
+
this.info(`[FluxStack] ${message}`, meta)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Export singleton instance
|
|
92
|
+
export const logger = Logger.getInstance()
|
|
93
|
+
|
|
94
|
+
// Export convenience functions
|
|
95
|
+
export const log = {
|
|
96
|
+
debug: (message: string, meta?: any) => logger.debug(message, meta),
|
|
97
|
+
info: (message: string, meta?: any) => logger.info(message, meta),
|
|
98
|
+
warn: (message: string, meta?: any) => logger.warn(message, meta),
|
|
99
|
+
error: (message: string, meta?: any) => logger.error(message, meta),
|
|
100
|
+
request: (method: string, path: string, status?: number, duration?: number) =>
|
|
101
|
+
logger.request(method, path, status, duration),
|
|
102
|
+
plugin: (pluginName: string, message: string, meta?: any) =>
|
|
103
|
+
logger.plugin(pluginName, message, meta),
|
|
104
|
+
framework: (message: string, meta?: any) =>
|
|
105
|
+
logger.framework(message, meta)
|
|
106
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
export interface Metric {
|
|
2
|
+
name: string
|
|
3
|
+
type: 'counter' | 'gauge' | 'histogram'
|
|
4
|
+
help: string
|
|
5
|
+
labels?: string[]
|
|
6
|
+
value?: number
|
|
7
|
+
values?: number[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Counter extends Metric {
|
|
11
|
+
type: 'counter'
|
|
12
|
+
inc(value?: number, labels?: Record<string, string>): void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Gauge extends Metric {
|
|
16
|
+
type: 'gauge'
|
|
17
|
+
set(value: number, labels?: Record<string, string>): void
|
|
18
|
+
inc(value?: number, labels?: Record<string, string>): void
|
|
19
|
+
dec(value?: number, labels?: Record<string, string>): void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Histogram extends Metric {
|
|
23
|
+
type: 'histogram'
|
|
24
|
+
observe(value: number, labels?: Record<string, string>): void
|
|
25
|
+
buckets: number[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SystemMetrics {
|
|
29
|
+
memoryUsage: {
|
|
30
|
+
rss: number
|
|
31
|
+
heapTotal: number
|
|
32
|
+
heapUsed: number
|
|
33
|
+
external: number
|
|
34
|
+
}
|
|
35
|
+
cpuUsage: {
|
|
36
|
+
user: number
|
|
37
|
+
system: number
|
|
38
|
+
}
|
|
39
|
+
eventLoopLag: number
|
|
40
|
+
uptime: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface HttpMetrics {
|
|
44
|
+
requestsTotal: number
|
|
45
|
+
requestDuration: number[]
|
|
46
|
+
requestSize: number[]
|
|
47
|
+
responseSize: number[]
|
|
48
|
+
errorRate: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class MetricsCollector {
|
|
52
|
+
private metrics: Map<string, Metric> = new Map()
|
|
53
|
+
private httpMetrics: HttpMetrics = {
|
|
54
|
+
requestsTotal: 0,
|
|
55
|
+
requestDuration: [],
|
|
56
|
+
requestSize: [],
|
|
57
|
+
responseSize: [],
|
|
58
|
+
errorRate: 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create metrics
|
|
62
|
+
createCounter(name: string, help: string, labels?: string[]): Counter {
|
|
63
|
+
const counter: Counter = {
|
|
64
|
+
name,
|
|
65
|
+
type: 'counter',
|
|
66
|
+
help,
|
|
67
|
+
labels,
|
|
68
|
+
value: 0,
|
|
69
|
+
inc: (value = 1, _labels) => {
|
|
70
|
+
counter.value = (counter.value || 0) + value
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.metrics.set(name, counter)
|
|
75
|
+
return counter
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
createGauge(name: string, help: string, labels?: string[]): Gauge {
|
|
79
|
+
const gauge: Gauge = {
|
|
80
|
+
name,
|
|
81
|
+
type: 'gauge',
|
|
82
|
+
help,
|
|
83
|
+
labels,
|
|
84
|
+
value: 0,
|
|
85
|
+
set: (value, _labels) => {
|
|
86
|
+
gauge.value = value
|
|
87
|
+
},
|
|
88
|
+
inc: (value = 1, _labels) => {
|
|
89
|
+
gauge.value = (gauge.value || 0) + value
|
|
90
|
+
},
|
|
91
|
+
dec: (value = 1, _labels) => {
|
|
92
|
+
gauge.value = (gauge.value || 0) - value
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.metrics.set(name, gauge)
|
|
97
|
+
return gauge
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
createHistogram(name: string, help: string, buckets: number[] = [0.1, 0.5, 1, 2.5, 5, 10]): Histogram {
|
|
101
|
+
const histogram: Histogram = {
|
|
102
|
+
name,
|
|
103
|
+
type: 'histogram',
|
|
104
|
+
help,
|
|
105
|
+
buckets,
|
|
106
|
+
values: [],
|
|
107
|
+
observe: (value, _labels) => {
|
|
108
|
+
histogram.values = histogram.values || []
|
|
109
|
+
histogram.values.push(value)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.metrics.set(name, histogram)
|
|
114
|
+
return histogram
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// HTTP metrics
|
|
118
|
+
recordHttpRequest(_method: string, _path: string, statusCode: number, duration: number, requestSize?: number, responseSize?: number): void {
|
|
119
|
+
this.httpMetrics.requestsTotal++
|
|
120
|
+
this.httpMetrics.requestDuration.push(duration)
|
|
121
|
+
|
|
122
|
+
if (requestSize) {
|
|
123
|
+
this.httpMetrics.requestSize.push(requestSize)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (responseSize) {
|
|
127
|
+
this.httpMetrics.responseSize.push(responseSize)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (statusCode >= 400) {
|
|
131
|
+
this.httpMetrics.errorRate = this.calculateErrorRate()
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// System metrics
|
|
136
|
+
getSystemMetrics(): SystemMetrics {
|
|
137
|
+
const memUsage = process.memoryUsage()
|
|
138
|
+
const cpuUsage = process.cpuUsage()
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
memoryUsage: {
|
|
142
|
+
rss: memUsage.rss,
|
|
143
|
+
heapTotal: memUsage.heapTotal,
|
|
144
|
+
heapUsed: memUsage.heapUsed,
|
|
145
|
+
external: memUsage.external
|
|
146
|
+
},
|
|
147
|
+
cpuUsage: {
|
|
148
|
+
user: cpuUsage.user,
|
|
149
|
+
system: cpuUsage.system
|
|
150
|
+
},
|
|
151
|
+
eventLoopLag: this.measureEventLoopLag(),
|
|
152
|
+
uptime: process.uptime()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Get all metrics
|
|
157
|
+
getAllMetrics(): Map<string, Metric> {
|
|
158
|
+
return new Map(this.metrics)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getHttpMetrics(): HttpMetrics {
|
|
162
|
+
return { ...this.httpMetrics }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Export metrics in Prometheus format
|
|
166
|
+
exportPrometheus(): string {
|
|
167
|
+
let output = ''
|
|
168
|
+
|
|
169
|
+
for (const metric of this.metrics.values()) {
|
|
170
|
+
output += `# HELP ${metric.name} ${metric.help}\n`
|
|
171
|
+
output += `# TYPE ${metric.name} ${metric.type}\n`
|
|
172
|
+
|
|
173
|
+
if (metric.type === 'counter' || metric.type === 'gauge') {
|
|
174
|
+
output += `${metric.name} ${metric.value || 0}\n`
|
|
175
|
+
} else if (metric.type === 'histogram' && metric.values) {
|
|
176
|
+
const values = metric.values.sort((a, b) => a - b)
|
|
177
|
+
const buckets = (metric as Histogram).buckets
|
|
178
|
+
|
|
179
|
+
for (const bucket of buckets) {
|
|
180
|
+
const count = values.filter(v => v <= bucket).length
|
|
181
|
+
output += `${metric.name}_bucket{le="${bucket}"} ${count}\n`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
output += `${metric.name}_bucket{le="+Inf"} ${values.length}\n`
|
|
185
|
+
output += `${metric.name}_count ${values.length}\n`
|
|
186
|
+
output += `${metric.name}_sum ${values.reduce((sum, v) => sum + v, 0)}\n`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
output += '\n'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return output
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private calculateErrorRate(): number {
|
|
196
|
+
const totalRequests = this.httpMetrics.requestsTotal
|
|
197
|
+
if (totalRequests === 0) return 0
|
|
198
|
+
|
|
199
|
+
// This is a simplified calculation - in a real implementation,
|
|
200
|
+
// you'd track error counts separately
|
|
201
|
+
return 0 // Placeholder
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private measureEventLoopLag(): number {
|
|
205
|
+
const start = process.hrtime.bigint()
|
|
206
|
+
setImmediate(() => {
|
|
207
|
+
const lag = Number(process.hrtime.bigint() - start) / 1e6 // Convert to milliseconds
|
|
208
|
+
return lag
|
|
209
|
+
})
|
|
210
|
+
return 0 // Placeholder - actual implementation would be more complex
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import { resolve, join } from 'path'
|
|
5
|
+
import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
import ora from 'ora'
|
|
8
|
+
|
|
9
|
+
const logo = `
|
|
10
|
+
ā” āāāāāāā āā āā āā āā āā āāāāāāā āāāāāāāā āāāāā āāāāāā āā āā
|
|
11
|
+
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
12
|
+
āāāāā āā āā āā āāā āāāāāāā āā āāāāāāā āā āāāāā
|
|
13
|
+
āā āā āā āā āā āā āā āā āā āā āā āā āā
|
|
14
|
+
āā āāāāāāā āāāāāā āā āā āāāāāāā āā āā āā āāāāāā āā āā
|
|
15
|
+
|
|
16
|
+
${chalk.cyan('š« Powered by Bun - The Divine Runtime ā”')}
|
|
17
|
+
${chalk.gray('Creates FluxStack apps by copying the working framework')}
|
|
18
|
+
`
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.name('create-fluxstack')
|
|
22
|
+
.description('ā” Create FluxStack apps with zero configuration')
|
|
23
|
+
.version('1.0.0')
|
|
24
|
+
.argument('[project-name]', 'Name of the project to create')
|
|
25
|
+
.option('--no-install', 'Skip dependency installation')
|
|
26
|
+
.option('--no-git', 'Skip git initialization')
|
|
27
|
+
.action(async (projectName, options) => {
|
|
28
|
+
console.clear()
|
|
29
|
+
console.log(chalk.magenta(logo))
|
|
30
|
+
|
|
31
|
+
if (!projectName || projectName.trim().length === 0) {
|
|
32
|
+
console.log(chalk.red('ā Project name is required'))
|
|
33
|
+
console.log(chalk.gray('Usage: ./create-fluxstack.ts my-app'))
|
|
34
|
+
process.exit(1)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const currentDir = import.meta.dir
|
|
38
|
+
const projectPath = resolve(projectName)
|
|
39
|
+
|
|
40
|
+
// Check if directory already exists
|
|
41
|
+
if (existsSync(projectPath)) {
|
|
42
|
+
console.log(chalk.red(`ā Directory ${projectName} already exists`))
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.cyan(`\\nš Creating FluxStack project: ${chalk.bold(projectName)}`))
|
|
47
|
+
console.log(chalk.gray(`š Location: ${projectPath}`))
|
|
48
|
+
|
|
49
|
+
// Create project directory
|
|
50
|
+
const spinner = ora('Creating project structure...').start()
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(projectPath, { recursive: true })
|
|
54
|
+
|
|
55
|
+
// Copy only essential FluxStack files (not node_modules, not test apps, etc.)
|
|
56
|
+
const filesToCopy = [
|
|
57
|
+
'core',
|
|
58
|
+
'app',
|
|
59
|
+
'package.json',
|
|
60
|
+
'bun.lock', // ā
CRITICAL: Copy lockfile to maintain working versions
|
|
61
|
+
'tsconfig.json',
|
|
62
|
+
'vite.config.ts',
|
|
63
|
+
'.env',
|
|
64
|
+
'README.md'
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for (const file of filesToCopy) {
|
|
68
|
+
const sourcePath = join(currentDir, file)
|
|
69
|
+
const destPath = join(projectPath, file)
|
|
70
|
+
|
|
71
|
+
if (existsSync(sourcePath)) {
|
|
72
|
+
cpSync(sourcePath, destPath, { recursive: true })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Customize package.json
|
|
77
|
+
const packageJsonPath = join(projectPath, 'package.json')
|
|
78
|
+
if (existsSync(packageJsonPath)) {
|
|
79
|
+
const packageContent = readFileSync(packageJsonPath, 'utf-8')
|
|
80
|
+
const packageJson = JSON.parse(packageContent)
|
|
81
|
+
|
|
82
|
+
packageJson.name = projectName
|
|
83
|
+
packageJson.description = `${projectName} - FluxStack application`
|
|
84
|
+
|
|
85
|
+
// Remove scripts that don't make sense for user projects
|
|
86
|
+
delete packageJson.scripts['test:watch']
|
|
87
|
+
delete packageJson.scripts['test:coverage']
|
|
88
|
+
delete packageJson.scripts['docs:build']
|
|
89
|
+
delete packageJson.scripts['docs:serve']
|
|
90
|
+
|
|
91
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Customize .env for development mode
|
|
95
|
+
const envPath = join(projectPath, '.env')
|
|
96
|
+
if (existsSync(envPath)) {
|
|
97
|
+
const envContent = readFileSync(envPath, 'utf-8')
|
|
98
|
+
const devEnvContent = envContent.replace(
|
|
99
|
+
'NODE_ENV=production',
|
|
100
|
+
'NODE_ENV=development'
|
|
101
|
+
)
|
|
102
|
+
writeFileSync(envPath, devEnvContent)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Customize README.md
|
|
106
|
+
const readmePath = join(projectPath, 'README.md')
|
|
107
|
+
if (existsSync(readmePath)) {
|
|
108
|
+
const readmeContent = `# ${projectName}
|
|
109
|
+
|
|
110
|
+
ā” **FluxStack Application** - Modern full-stack TypeScript framework
|
|
111
|
+
|
|
112
|
+
## š Getting Started
|
|
113
|
+
|
|
114
|
+
\`\`\`bash
|
|
115
|
+
# Start development
|
|
116
|
+
bun run dev
|
|
117
|
+
|
|
118
|
+
# Build for production
|
|
119
|
+
bun run build
|
|
120
|
+
|
|
121
|
+
# Start production server
|
|
122
|
+
bun run start
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
## š Project Structure
|
|
126
|
+
|
|
127
|
+
\`\`\`
|
|
128
|
+
${projectName}/
|
|
129
|
+
āāā core/ # FluxStack framework (don't modify)
|
|
130
|
+
āāā app/ # Your application code
|
|
131
|
+
ā āāā server/ # Backend API routes
|
|
132
|
+
ā āāā client/ # Frontend React app
|
|
133
|
+
ā āāā shared/ # Shared types and utilities
|
|
134
|
+
āāā package.json
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
## š„ Features
|
|
138
|
+
|
|
139
|
+
- **ā” Bun Runtime** - 3x faster than Node.js
|
|
140
|
+
- **š Full Type Safety** - Eden Treaty + TypeScript
|
|
141
|
+
- **šØ Modern UI** - React 19 + Tailwind CSS v4
|
|
142
|
+
- **š Auto Documentation** - Swagger UI generated
|
|
143
|
+
- **š Hot Reload** - Backend + Frontend
|
|
144
|
+
|
|
145
|
+
## š Learn More
|
|
146
|
+
|
|
147
|
+
Visit the [FluxStack Documentation](https://fluxstack.dev) to learn more!
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
Built with ā¤ļø using FluxStack
|
|
152
|
+
`
|
|
153
|
+
writeFileSync(readmePath, readmeContent)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
spinner.succeed('ā
Project structure created!')
|
|
157
|
+
|
|
158
|
+
// Install dependencies with Bun (THE DIVINE RUNTIME)
|
|
159
|
+
if (options.install) {
|
|
160
|
+
const installSpinner = ora('š¦ Installing dependencies with Bun...').start()
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const proc = Bun.spawn(['bun', 'install'], {
|
|
164
|
+
cwd: projectPath,
|
|
165
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await proc.exited
|
|
169
|
+
|
|
170
|
+
if (proc.exitCode === 0) {
|
|
171
|
+
installSpinner.succeed('ā
Dependencies installed!')
|
|
172
|
+
} else {
|
|
173
|
+
installSpinner.fail('ā Failed to install dependencies')
|
|
174
|
+
console.log(chalk.gray('You can install them manually with: bun install'))
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
installSpinner.fail('ā Failed to install dependencies')
|
|
178
|
+
console.log(chalk.gray('You can install them manually with: bun install'))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Initialize git
|
|
183
|
+
if (options.git) {
|
|
184
|
+
const gitSpinner = ora('š Initializing git repository...').start()
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const initProc = Bun.spawn(['git', 'init'], {
|
|
188
|
+
cwd: projectPath,
|
|
189
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
190
|
+
})
|
|
191
|
+
await initProc.exited
|
|
192
|
+
|
|
193
|
+
// Create initial commit
|
|
194
|
+
const addProc = Bun.spawn(['git', 'add', '.'], {
|
|
195
|
+
cwd: projectPath,
|
|
196
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
197
|
+
})
|
|
198
|
+
await addProc.exited
|
|
199
|
+
|
|
200
|
+
const commitProc = Bun.spawn(['git', 'commit', '-m', `feat: initial ${projectName} with FluxStack`], {
|
|
201
|
+
cwd: projectPath,
|
|
202
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
203
|
+
})
|
|
204
|
+
await commitProc.exited
|
|
205
|
+
|
|
206
|
+
gitSpinner.succeed('ā
Git repository initialized!')
|
|
207
|
+
} catch (error) {
|
|
208
|
+
gitSpinner.fail('ā Failed to initialize git')
|
|
209
|
+
console.log(chalk.gray('You can initialize it manually with: git init'))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Success message
|
|
214
|
+
console.log(chalk.green('\\nš Project created successfully!'))
|
|
215
|
+
console.log(chalk.cyan('\\nNext steps:'))
|
|
216
|
+
console.log(chalk.white(` cd ${projectName}`))
|
|
217
|
+
if (!options.install) {
|
|
218
|
+
console.log(chalk.white(` bun install`))
|
|
219
|
+
}
|
|
220
|
+
console.log(chalk.white(` bun run dev`))
|
|
221
|
+
console.log(chalk.magenta('\\nHappy coding with the divine Bun runtime! ā”š„'))
|
|
222
|
+
console.log(chalk.gray('\\nVisit http://localhost:3000 when ready!'))
|
|
223
|
+
|
|
224
|
+
} catch (error) {
|
|
225
|
+
spinner.fail('ā Failed to create project')
|
|
226
|
+
console.error(error)
|
|
227
|
+
process.exit(1)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
program.parse()
|