create-fluxstack 1.7.4 → 1.8.1
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/.dockerignore +82 -0
- package/Dockerfile +70 -0
- package/app/server/app.ts +20 -5
- package/app/server/backend-only.ts +15 -12
- package/app/server/index.ts +83 -96
- package/app/server/live/FluxStackConfig.ts +5 -5
- package/app/server/routes/env-test.ts +59 -0
- package/config/app.config.ts +2 -54
- package/config/client.config.ts +95 -0
- package/config/index.ts +57 -22
- package/config/monitoring.config.ts +114 -0
- package/config/plugins.config.ts +59 -0
- package/config/runtime.config.ts +0 -17
- package/config/server.config.ts +50 -30
- package/core/build/bundler.ts +17 -16
- package/core/build/flux-plugins-generator.ts +29 -18
- package/core/build/index.ts +72 -65
- package/core/build/live-components-generator.ts +29 -18
- package/core/build/optimizer.ts +37 -17
- package/core/cli/index.ts +6 -2
- package/core/config/env.ts +4 -0
- package/core/config/runtime-config.ts +10 -8
- package/core/config/schema.ts +24 -2
- package/core/framework/server.ts +1 -0
- package/core/index.ts +31 -23
- package/core/plugins/built-in/monitoring/index.ts +2 -0
- package/core/plugins/built-in/static/index.ts +73 -244
- package/core/plugins/built-in/swagger/index.ts +2 -0
- package/core/plugins/built-in/vite/index.ts +377 -374
- package/core/plugins/config.ts +2 -0
- package/core/plugins/discovery.ts +2 -0
- package/core/plugins/executor.ts +2 -0
- package/core/plugins/index.ts +2 -2
- package/core/plugins/registry.ts +22 -18
- package/core/server/backend-entry.ts +51 -0
- package/core/types/plugin.ts +6 -0
- package/core/utils/build-logger.ts +324 -0
- package/core/utils/config-schema.ts +2 -6
- package/core/utils/helpers.ts +14 -9
- package/core/utils/regenerate-files.ts +69 -0
- package/core/utils/version.ts +1 -1
- package/fluxstack.config.ts +138 -252
- package/package.json +2 -17
- package/vitest.config.ts +8 -26
- package/config/build.config.ts +0 -24
package/core/plugins/config.ts
CHANGED
|
@@ -7,6 +7,8 @@ import type { FluxStack, PluginConfigSchema, PluginValidationResult } from "./ty
|
|
|
7
7
|
import type { FluxStackConfig } from "../config/schema"
|
|
8
8
|
import type { Logger } from "../utils/logger/index"
|
|
9
9
|
|
|
10
|
+
type Plugin = FluxStack.Plugin
|
|
11
|
+
|
|
10
12
|
export interface PluginConfigManager {
|
|
11
13
|
validatePluginConfig(plugin: Plugin, config: any): PluginValidationResult
|
|
12
14
|
mergePluginConfig(plugin: Plugin, userConfig: any): any
|
package/core/plugins/executor.ts
CHANGED
package/core/plugins/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ export type {
|
|
|
27
27
|
BuildContext
|
|
28
28
|
} from './types'
|
|
29
29
|
|
|
30
|
-
export type { FluxStack }
|
|
31
30
|
export type Plugin = FluxStack.Plugin
|
|
32
31
|
|
|
33
32
|
// Plugin registry
|
|
@@ -200,5 +199,6 @@ import type {
|
|
|
200
199
|
PluginPriority,
|
|
201
200
|
RequestContext,
|
|
202
201
|
ResponseContext,
|
|
203
|
-
ErrorContext
|
|
202
|
+
ErrorContext,
|
|
203
|
+
FluxStack
|
|
204
204
|
} from './types'
|
package/core/plugins/registry.ts
CHANGED
|
@@ -291,7 +291,21 @@ export class PluginRegistry {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
//
|
|
294
|
+
// Install dependencies BEFORE importing the plugin
|
|
295
|
+
if (manifest && manifest.dependencies && Object.keys(manifest.dependencies).length > 0) {
|
|
296
|
+
try {
|
|
297
|
+
const resolution = await this.dependencyManager.resolvePluginDependencies(pluginPath)
|
|
298
|
+
if (resolution.dependencies.length > 0) {
|
|
299
|
+
// Install dependencies directly in the plugin directory
|
|
300
|
+
await this.dependencyManager.installPluginDependenciesLocally(pluginPath, resolution.dependencies)
|
|
301
|
+
this.logger?.debug(`Dependencies installed for plugin at: ${pluginPath}`)
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
this.logger?.warn(`Failed to install dependencies for plugin at '${pluginPath}'`, { error })
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Try to import the plugin (after dependencies are installed)
|
|
295
309
|
const pluginModule = await import(resolve(pluginPath))
|
|
296
310
|
const plugin: FluxStackPlugin = pluginModule.default || pluginModule
|
|
297
311
|
|
|
@@ -434,38 +448,28 @@ export class PluginRegistry {
|
|
|
434
448
|
* Resolver dependências de todos os plugins descobertos
|
|
435
449
|
*/
|
|
436
450
|
private async resolveDependencies(results: PluginLoadResult[]): Promise<void> {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
451
|
+
// Dependencies are now installed during plugin loading in loadPlugin()
|
|
452
|
+
// This method is kept for compatibility but no longer performs installation
|
|
453
|
+
|
|
454
|
+
// Only check for dependency conflicts on successfully loaded plugins
|
|
440
455
|
for (const result of results) {
|
|
441
456
|
if (result.success && result.plugin) {
|
|
442
457
|
try {
|
|
443
|
-
// Tentar encontrar o diretório do plugin
|
|
444
458
|
const pluginDir = this.findPluginDirectory(result.plugin.name)
|
|
445
459
|
if (pluginDir) {
|
|
446
460
|
const resolution = await this.dependencyManager.resolvePluginDependencies(pluginDir)
|
|
447
|
-
|
|
448
|
-
|
|
461
|
+
|
|
449
462
|
if (!resolution.resolved) {
|
|
450
|
-
this.logger?.warn(`Plugin '${result.plugin.name}'
|
|
463
|
+
this.logger?.warn(`Plugin '${result.plugin.name}' has dependency conflicts`, {
|
|
451
464
|
conflicts: resolution.conflicts.length
|
|
452
465
|
})
|
|
453
466
|
}
|
|
454
467
|
}
|
|
455
468
|
} catch (error) {
|
|
456
|
-
this.logger?.warn(`
|
|
469
|
+
this.logger?.warn(`Failed to check dependencies for plugin '${result.plugin.name}'`, { error })
|
|
457
470
|
}
|
|
458
471
|
}
|
|
459
472
|
}
|
|
460
|
-
|
|
461
|
-
// Instalar dependências se houver resoluções
|
|
462
|
-
if (resolutions.length > 0) {
|
|
463
|
-
try {
|
|
464
|
-
await this.dependencyManager.installPluginDependencies(resolutions)
|
|
465
|
-
} catch (error) {
|
|
466
|
-
this.logger?.error('Erro ao instalar dependências de plugins', { error })
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
473
|
}
|
|
470
474
|
|
|
471
475
|
/**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend Entry Point - Core Framework
|
|
3
|
+
*
|
|
4
|
+
* This file contains the protected logic for running backend standalone mode.
|
|
5
|
+
* DO NOT modify this file directly - it's part of the FluxStack framework core.
|
|
6
|
+
*
|
|
7
|
+
* For customization, use app/server/backend-only.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Elysia } from "elysia"
|
|
11
|
+
import { startBackendOnly } from "./standalone"
|
|
12
|
+
|
|
13
|
+
export interface BackendEntryConfig {
|
|
14
|
+
port: number
|
|
15
|
+
apiPrefix?: string
|
|
16
|
+
host?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Start backend in standalone mode
|
|
21
|
+
*
|
|
22
|
+
* @param apiRoutes - Elysia routes from app/server/routes
|
|
23
|
+
* @param config - Backend configuration
|
|
24
|
+
*/
|
|
25
|
+
export function startBackend(
|
|
26
|
+
apiRoutes: Elysia,
|
|
27
|
+
config: BackendEntryConfig
|
|
28
|
+
) {
|
|
29
|
+
const { port, apiPrefix = '/api', host = 'localhost' } = config
|
|
30
|
+
|
|
31
|
+
console.log(`🚀 Backend standalone: ${host}:${port}`)
|
|
32
|
+
console.log(`📡 API Prefix: ${apiPrefix}`)
|
|
33
|
+
console.log()
|
|
34
|
+
|
|
35
|
+
// Start backend using the standalone utility
|
|
36
|
+
startBackendOnly(apiRoutes, { port, apiPrefix })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create backend entry config from declarative config
|
|
41
|
+
* Helper to make it easy to use with the config system
|
|
42
|
+
*/
|
|
43
|
+
export function createBackendConfig(
|
|
44
|
+
serverConfig: { server: { backendPort: number; apiPrefix: string; host: string } }
|
|
45
|
+
): BackendEntryConfig {
|
|
46
|
+
return {
|
|
47
|
+
port: serverConfig.server.backendPort,
|
|
48
|
+
apiPrefix: serverConfig.server.apiPrefix,
|
|
49
|
+
host: serverConfig.server.host
|
|
50
|
+
}
|
|
51
|
+
}
|
package/core/types/plugin.ts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Comprehensive type definitions for the plugin system
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// Import namespace for type alias
|
|
7
|
+
import type { FluxStack } from "../plugins/types"
|
|
8
|
+
|
|
6
9
|
// Re-export plugin types
|
|
7
10
|
export type {
|
|
8
11
|
FluxStack,
|
|
@@ -13,6 +16,9 @@ export type {
|
|
|
13
16
|
ErrorContext
|
|
14
17
|
} from "../plugins/types"
|
|
15
18
|
|
|
19
|
+
// Export Plugin as a standalone type for convenience
|
|
20
|
+
export type Plugin = FluxStack.Plugin
|
|
21
|
+
|
|
16
22
|
// Additional plugin-related types
|
|
17
23
|
export interface PluginManifest {
|
|
18
24
|
name: string
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FluxStack Build Logger - Beautiful terminal output for build process
|
|
3
|
+
* Provides formatted tables, boxes, and colored output
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ANSI Color codes
|
|
7
|
+
const colors = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bright: '\x1b[1m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
|
|
12
|
+
// Text colors
|
|
13
|
+
cyan: '\x1b[36m',
|
|
14
|
+
blue: '\x1b[34m',
|
|
15
|
+
green: '\x1b[32m',
|
|
16
|
+
yellow: '\x1b[33m',
|
|
17
|
+
red: '\x1b[31m',
|
|
18
|
+
magenta: '\x1b[35m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
gray: '\x1b[90m',
|
|
21
|
+
|
|
22
|
+
// Background colors
|
|
23
|
+
bgCyan: '\x1b[46m',
|
|
24
|
+
bgBlue: '\x1b[44m',
|
|
25
|
+
bgGreen: '\x1b[42m',
|
|
26
|
+
bgYellow: '\x1b[43m',
|
|
27
|
+
bgRed: '\x1b[41m',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Box drawing characters
|
|
31
|
+
const box = {
|
|
32
|
+
topLeft: '╭',
|
|
33
|
+
topRight: '╮',
|
|
34
|
+
bottomLeft: '╰',
|
|
35
|
+
bottomRight: '╯',
|
|
36
|
+
horizontal: '─',
|
|
37
|
+
vertical: '│',
|
|
38
|
+
verticalRight: '├',
|
|
39
|
+
verticalLeft: '┤',
|
|
40
|
+
horizontalDown: '┬',
|
|
41
|
+
horizontalUp: '┴',
|
|
42
|
+
cross: '┼',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TableColumn {
|
|
46
|
+
header: string
|
|
47
|
+
key: string
|
|
48
|
+
width?: number
|
|
49
|
+
align?: 'left' | 'right' | 'center'
|
|
50
|
+
color?: keyof typeof colors
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TableRow {
|
|
54
|
+
[key: string]: string | number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class BuildLogger {
|
|
58
|
+
private indent = ''
|
|
59
|
+
private startTime = Date.now()
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Print a beautiful header banner
|
|
63
|
+
*/
|
|
64
|
+
header(title: string) {
|
|
65
|
+
const width = 60
|
|
66
|
+
const padding = Math.floor((width - title.length - 2) / 2)
|
|
67
|
+
const paddingRight = width - title.length - 2 - padding
|
|
68
|
+
|
|
69
|
+
console.log()
|
|
70
|
+
console.log(colors.cyan + colors.bright + box.topLeft + box.horizontal.repeat(width) + box.topRight + colors.reset)
|
|
71
|
+
console.log(colors.cyan + box.vertical + ' '.repeat(padding) + colors.bright + colors.white + title + colors.cyan + ' '.repeat(paddingRight) + box.vertical + colors.reset)
|
|
72
|
+
console.log(colors.cyan + box.bottomLeft + box.horizontal.repeat(width) + box.bottomRight + colors.reset)
|
|
73
|
+
console.log()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Print a section header
|
|
78
|
+
*/
|
|
79
|
+
section(title: string, icon: string = '📦') {
|
|
80
|
+
console.log()
|
|
81
|
+
console.log(colors.bright + colors.blue + `${icon} ${title}` + colors.reset)
|
|
82
|
+
console.log(colors.dim + colors.gray + box.horizontal.repeat(50) + colors.reset)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Print a success message
|
|
87
|
+
*/
|
|
88
|
+
success(message: string) {
|
|
89
|
+
console.log(colors.green + '✓ ' + colors.reset + message)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Print an error message
|
|
94
|
+
*/
|
|
95
|
+
error(message: string) {
|
|
96
|
+
console.log(colors.red + '✗ ' + colors.reset + message)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Print a warning message
|
|
101
|
+
*/
|
|
102
|
+
warn(message: string) {
|
|
103
|
+
console.log(colors.yellow + '⚠ ' + colors.reset + message)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Print an info message
|
|
108
|
+
*/
|
|
109
|
+
info(message: string, icon: string = '→') {
|
|
110
|
+
console.log(colors.cyan + icon + ' ' + colors.reset + message)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Print a step message
|
|
115
|
+
*/
|
|
116
|
+
step(message: string, icon: string = '▸') {
|
|
117
|
+
console.log(colors.dim + colors.gray + icon + ' ' + colors.reset + message)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Print a table
|
|
122
|
+
*/
|
|
123
|
+
table(columns: TableColumn[], rows: TableRow[]) {
|
|
124
|
+
if (rows.length === 0) {
|
|
125
|
+
this.warn('No data to display')
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Calculate column widths
|
|
130
|
+
const widths = columns.map(col => {
|
|
131
|
+
if (col.width) return col.width
|
|
132
|
+
const maxContentWidth = Math.max(
|
|
133
|
+
col.header.length,
|
|
134
|
+
...rows.map(row => String(row[col.key] || '').length)
|
|
135
|
+
)
|
|
136
|
+
return Math.min(maxContentWidth, 40) // Max 40 chars per column
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const totalWidth = widths.reduce((sum, w) => sum + w, 0) + (columns.length - 1) * 3 + 4
|
|
140
|
+
|
|
141
|
+
// Print top border
|
|
142
|
+
console.log(
|
|
143
|
+
colors.gray + box.topLeft +
|
|
144
|
+
widths.map((w, i) =>
|
|
145
|
+
box.horizontal.repeat(w + 2) + (i < widths.length - 1 ? box.horizontalDown : '')
|
|
146
|
+
).join('') +
|
|
147
|
+
box.topRight + colors.reset
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Print header
|
|
151
|
+
const headerRow = columns.map((col, i) => {
|
|
152
|
+
const content = this.padContent(col.header, widths[i], 'center')
|
|
153
|
+
return colors.bright + colors.white + content + colors.reset
|
|
154
|
+
}).join(colors.gray + ' │ ' + colors.reset)
|
|
155
|
+
|
|
156
|
+
console.log(colors.gray + box.vertical + ' ' + colors.reset + headerRow + colors.gray + ' ' + box.vertical + colors.reset)
|
|
157
|
+
|
|
158
|
+
// Print header separator
|
|
159
|
+
console.log(
|
|
160
|
+
colors.gray + box.verticalRight +
|
|
161
|
+
widths.map((w, i) =>
|
|
162
|
+
box.horizontal.repeat(w + 2) + (i < widths.length - 1 ? box.cross : '')
|
|
163
|
+
).join('') +
|
|
164
|
+
box.verticalLeft + colors.reset
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
// Print rows
|
|
168
|
+
rows.forEach((row, rowIndex) => {
|
|
169
|
+
const rowContent = columns.map((col, i) => {
|
|
170
|
+
const value = String(row[col.key] || '')
|
|
171
|
+
const content = this.padContent(value, widths[i], col.align || 'left')
|
|
172
|
+
const color = col.color ? colors[col.color] : ''
|
|
173
|
+
return color + content + colors.reset
|
|
174
|
+
}).join(colors.gray + ' │ ' + colors.reset)
|
|
175
|
+
|
|
176
|
+
console.log(colors.gray + box.vertical + ' ' + colors.reset + rowContent + colors.gray + ' ' + box.vertical + colors.reset)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// Print bottom border
|
|
180
|
+
console.log(
|
|
181
|
+
colors.gray + box.bottomLeft +
|
|
182
|
+
widths.map((w, i) =>
|
|
183
|
+
box.horizontal.repeat(w + 2) + (i < widths.length - 1 ? box.horizontalUp : '')
|
|
184
|
+
).join('') +
|
|
185
|
+
box.bottomRight + colors.reset
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Print a simple info box
|
|
191
|
+
*/
|
|
192
|
+
box(title: string, items: Array<{ label: string; value: string | number; color?: keyof typeof colors }>) {
|
|
193
|
+
const maxLabelWidth = Math.max(...items.map(i => i.label.length))
|
|
194
|
+
const maxValueWidth = Math.max(...items.map(i => String(i.value).length))
|
|
195
|
+
const contentWidth = maxLabelWidth + maxValueWidth + 3
|
|
196
|
+
const boxWidth = Math.max(contentWidth, title.length) + 4
|
|
197
|
+
|
|
198
|
+
// Top border with title
|
|
199
|
+
console.log()
|
|
200
|
+
console.log(colors.cyan + box.topLeft + box.horizontal.repeat(2) + colors.bright + colors.white + title + colors.cyan + box.horizontal.repeat(boxWidth - title.length - 2) + box.topRight + colors.reset)
|
|
201
|
+
|
|
202
|
+
// Content
|
|
203
|
+
items.forEach(item => {
|
|
204
|
+
const label = item.label.padEnd(maxLabelWidth)
|
|
205
|
+
const value = String(item.value)
|
|
206
|
+
const valueColor = item.color ? colors[item.color] : colors.white
|
|
207
|
+
console.log(
|
|
208
|
+
colors.cyan + box.vertical + ' ' + colors.reset +
|
|
209
|
+
colors.gray + label + colors.reset +
|
|
210
|
+
colors.dim + ' : ' + colors.reset +
|
|
211
|
+
valueColor + colors.bright + value + colors.reset +
|
|
212
|
+
' '.repeat(boxWidth - label.length - value.length - 3) +
|
|
213
|
+
colors.cyan + box.vertical + colors.reset
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Bottom border
|
|
218
|
+
console.log(colors.cyan + box.bottomLeft + box.horizontal.repeat(boxWidth) + box.bottomRight + colors.reset)
|
|
219
|
+
console.log()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Print a progress indicator
|
|
224
|
+
*/
|
|
225
|
+
progress(current: number, total: number, label: string) {
|
|
226
|
+
const percentage = Math.round((current / total) * 100)
|
|
227
|
+
const barLength = 30
|
|
228
|
+
const filled = Math.round((percentage / 100) * barLength)
|
|
229
|
+
const empty = barLength - filled
|
|
230
|
+
|
|
231
|
+
const bar = colors.green + '█'.repeat(filled) + colors.gray + '░'.repeat(empty) + colors.reset
|
|
232
|
+
console.log(`${label} [${bar}] ${percentage}% (${current}/${total})`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Start a timer
|
|
237
|
+
*/
|
|
238
|
+
startTimer(label?: string) {
|
|
239
|
+
this.startTime = Date.now()
|
|
240
|
+
if (label) {
|
|
241
|
+
this.info(label, '⏱')
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* End timer and print elapsed time
|
|
247
|
+
*/
|
|
248
|
+
endTimer(label: string = 'Completed') {
|
|
249
|
+
const elapsed = Date.now() - this.startTime
|
|
250
|
+
const seconds = (elapsed / 1000).toFixed(2)
|
|
251
|
+
this.success(`${label} in ${colors.bright}${seconds}s${colors.reset}`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Print a summary box
|
|
256
|
+
*/
|
|
257
|
+
summary(title: string, stats: Array<{ label: string; value: string | number; highlight?: boolean }>) {
|
|
258
|
+
console.log()
|
|
259
|
+
console.log(colors.bright + colors.green + '╔═══════════════════════════════════════════════════════════╗' + colors.reset)
|
|
260
|
+
console.log(colors.bright + colors.green + '║' + colors.reset + colors.bright + colors.white + ` ${title}`.padEnd(60) + colors.bright + colors.green + '║' + colors.reset)
|
|
261
|
+
console.log(colors.bright + colors.green + '╠═══════════════════════════════════════════════════════════╣' + colors.reset)
|
|
262
|
+
|
|
263
|
+
stats.forEach(stat => {
|
|
264
|
+
const label = ` ${stat.label}:`
|
|
265
|
+
const value = String(stat.value)
|
|
266
|
+
const valueColor = stat.highlight ? colors.yellow + colors.bright : colors.white
|
|
267
|
+
const padding = 60 - label.length - value.length - 1
|
|
268
|
+
console.log(
|
|
269
|
+
colors.bright + colors.green + '║' + colors.reset +
|
|
270
|
+
colors.cyan + label + colors.reset +
|
|
271
|
+
' '.repeat(Math.max(padding, 1)) +
|
|
272
|
+
valueColor + value + colors.reset +
|
|
273
|
+
colors.bright + colors.green + ' ║' + colors.reset
|
|
274
|
+
)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
console.log(colors.bright + colors.green + '╚═══════════════════════════════════════════════════════════╝' + colors.reset)
|
|
278
|
+
console.log()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Pad content based on alignment
|
|
283
|
+
*/
|
|
284
|
+
private padContent(content: string, width: number, align: 'left' | 'right' | 'center' = 'left'): string {
|
|
285
|
+
if (content.length >= width) {
|
|
286
|
+
return content.slice(0, width)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const padding = width - content.length
|
|
290
|
+
|
|
291
|
+
switch (align) {
|
|
292
|
+
case 'right':
|
|
293
|
+
return ' '.repeat(padding) + content
|
|
294
|
+
case 'center':
|
|
295
|
+
const leftPad = Math.floor(padding / 2)
|
|
296
|
+
const rightPad = padding - leftPad
|
|
297
|
+
return ' '.repeat(leftPad) + content + ' '.repeat(rightPad)
|
|
298
|
+
default:
|
|
299
|
+
return content + ' '.repeat(padding)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Format file size
|
|
305
|
+
*/
|
|
306
|
+
formatSize(bytes: number): string {
|
|
307
|
+
if (bytes === 0) return '0 B'
|
|
308
|
+
const k = 1024
|
|
309
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
310
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
311
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Format duration
|
|
316
|
+
*/
|
|
317
|
+
formatDuration(ms: number): string {
|
|
318
|
+
if (ms < 1000) return `${ms}ms`
|
|
319
|
+
return `${(ms / 1000).toFixed(2)}s`
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Export singleton instance
|
|
324
|
+
export const buildLogger = new BuildLogger()
|
|
@@ -23,17 +23,13 @@
|
|
|
23
23
|
* default: 3000,
|
|
24
24
|
* validate: (value) => value > 0 && value < 65536
|
|
25
25
|
* },
|
|
26
|
-
*
|
|
27
|
-
* type: 'boolean',
|
|
28
|
-
* env: 'DEBUG',
|
|
29
|
-
* default: false
|
|
30
|
-
* }
|
|
26
|
+
* env: config.enum('NODE_ENV', ['development', 'production', 'test'] as const, 'development', true)
|
|
31
27
|
* })
|
|
32
28
|
*
|
|
33
29
|
* // Access with full type safety
|
|
34
30
|
* appConfig.name // string
|
|
35
31
|
* appConfig.port // number
|
|
36
|
-
* appConfig.
|
|
32
|
+
* appConfig.env // "development" | "production" | "test"
|
|
37
33
|
* ```
|
|
38
34
|
*/
|
|
39
35
|
|
package/core/utils/helpers.ts
CHANGED
|
@@ -86,22 +86,27 @@ export const throttle = <T extends (...args: any[]) => any>(
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Environment detection utilities
|
|
91
|
+
* Uses declarative config system instead of legacy env
|
|
92
|
+
*/
|
|
93
|
+
|
|
89
94
|
export const isProduction = (): boolean => {
|
|
90
|
-
//
|
|
91
|
-
const {
|
|
92
|
-
return env
|
|
95
|
+
// Lazy import to avoid circular dependency during module initialization
|
|
96
|
+
const { appConfig } = require('@/config/app.config')
|
|
97
|
+
return appConfig.env === 'production'
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
export const isDevelopment = (): boolean => {
|
|
96
|
-
//
|
|
97
|
-
const {
|
|
98
|
-
return env
|
|
101
|
+
// Lazy import to avoid circular dependency during module initialization
|
|
102
|
+
const { appConfig } = require('@/config/app.config')
|
|
103
|
+
return appConfig.env === 'development'
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
export const isTest = (): boolean => {
|
|
102
|
-
//
|
|
103
|
-
const {
|
|
104
|
-
return env
|
|
107
|
+
// Lazy import to avoid circular dependency during module initialization
|
|
108
|
+
const { appConfig } = require('@/config/app.config')
|
|
109
|
+
return appConfig.env === 'test'
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
export const deepMerge = <T extends Record<string, any>>(target: T, source: Partial<T>): T => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Regeneration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions to regenerate critical application files that might be
|
|
5
|
+
* accidentally deleted by developers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { join } from "path"
|
|
9
|
+
import { existsSync } from "fs"
|
|
10
|
+
|
|
11
|
+
const BACKEND_ONLY_TEMPLATE = `/**
|
|
12
|
+
* Backend Standalone Entry Point
|
|
13
|
+
*
|
|
14
|
+
* This is a minimal wrapper for starting the backend in standalone mode.
|
|
15
|
+
* The core logic is protected in @/core/server/backend-entry.ts
|
|
16
|
+
*
|
|
17
|
+
* You can customize the configuration here if needed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { startBackend, createBackendConfig } from "@/core/server/backend-entry"
|
|
21
|
+
import { apiRoutes } from "./routes"
|
|
22
|
+
import { serverConfig } from "@/config/server.config"
|
|
23
|
+
|
|
24
|
+
// Create backend configuration from declarative config
|
|
25
|
+
const backendConfig = createBackendConfig(serverConfig)
|
|
26
|
+
|
|
27
|
+
// Start backend in standalone mode
|
|
28
|
+
startBackend(apiRoutes, backendConfig)
|
|
29
|
+
`
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if backend-only.ts exists, regenerate if missing
|
|
33
|
+
*/
|
|
34
|
+
export async function ensureBackendEntry(projectRoot: string = process.cwd()): Promise<boolean> {
|
|
35
|
+
const backendOnlyPath = join(projectRoot, "app/server/backend-only.ts")
|
|
36
|
+
|
|
37
|
+
if (!existsSync(backendOnlyPath)) {
|
|
38
|
+
console.log("⚠️ backend-only.ts not found, regenerating...")
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await Bun.write(backendOnlyPath, BACKEND_ONLY_TEMPLATE)
|
|
42
|
+
console.log("✅ backend-only.ts regenerated successfully")
|
|
43
|
+
return true
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("❌ Failed to regenerate backend-only.ts:", error)
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Regenerate backend-only.ts file
|
|
55
|
+
*/
|
|
56
|
+
export async function regenerateBackendEntry(projectRoot: string = process.cwd()): Promise<boolean> {
|
|
57
|
+
const backendOnlyPath = join(projectRoot, "app/server/backend-only.ts")
|
|
58
|
+
|
|
59
|
+
console.log("🔄 Regenerating backend-only.ts...")
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await Bun.write(backendOnlyPath, BACKEND_ONLY_TEMPLATE)
|
|
63
|
+
console.log("✅ backend-only.ts regenerated successfully")
|
|
64
|
+
return true
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("❌ Failed to regenerate backend-only.ts:", error)
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
}
|
package/core/utils/version.ts
CHANGED