create-fluxstack 1.10.1 → 1.12.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/.dockerignore +1 -2
- package/Dockerfile +8 -8
- package/LLMD/INDEX.md +64 -0
- package/LLMD/MAINTENANCE.md +197 -0
- package/LLMD/MIGRATION.md +156 -0
- package/LLMD/config/.gitkeep +1 -0
- package/LLMD/config/declarative-system.md +268 -0
- package/LLMD/config/environment-vars.md +327 -0
- package/LLMD/config/runtime-reload.md +401 -0
- package/LLMD/core/.gitkeep +1 -0
- package/LLMD/core/build-system.md +599 -0
- package/LLMD/core/framework-lifecycle.md +229 -0
- package/LLMD/core/plugin-system.md +451 -0
- package/LLMD/patterns/.gitkeep +1 -0
- package/LLMD/patterns/anti-patterns.md +297 -0
- package/LLMD/patterns/project-structure.md +264 -0
- package/LLMD/patterns/type-safety.md +440 -0
- package/LLMD/reference/.gitkeep +1 -0
- package/LLMD/reference/cli-commands.md +250 -0
- package/LLMD/reference/plugin-hooks.md +357 -0
- package/LLMD/reference/routing.md +39 -0
- package/LLMD/reference/troubleshooting.md +364 -0
- package/LLMD/resources/.gitkeep +1 -0
- package/LLMD/resources/controllers.md +465 -0
- package/LLMD/resources/live-components.md +703 -0
- package/LLMD/resources/live-rooms.md +482 -0
- package/LLMD/resources/live-upload.md +130 -0
- package/LLMD/resources/plugins-external.md +617 -0
- package/LLMD/resources/routes-eden.md +254 -0
- package/README.md +37 -17
- package/app/client/index.html +0 -1
- package/app/client/src/App.tsx +107 -150
- package/app/client/src/components/AppLayout.tsx +68 -0
- package/app/client/src/components/BackButton.tsx +13 -0
- package/app/client/src/components/DemoPage.tsx +20 -0
- package/app/client/src/components/LiveUploadWidget.tsx +204 -0
- package/app/client/src/lib/eden-api.ts +85 -60
- package/app/client/src/live/ChatDemo.tsx +107 -0
- package/app/client/src/live/CounterDemo.tsx +206 -0
- package/app/client/src/live/FormDemo.tsx +119 -0
- package/app/client/src/live/RoomChatDemo.tsx +242 -0
- package/app/client/src/live/UploadDemo.tsx +21 -0
- package/app/client/src/main.tsx +4 -1
- package/app/client/src/pages/ApiTestPage.tsx +108 -0
- package/app/client/src/pages/HomePage.tsx +76 -0
- package/app/server/app.ts +1 -4
- package/app/server/controllers/users.controller.ts +36 -44
- package/app/server/index.ts +25 -35
- package/app/server/live/LiveChat.ts +77 -0
- package/app/server/live/LiveCounter.ts +67 -0
- package/app/server/live/LiveForm.ts +63 -0
- package/app/server/live/LiveLocalCounter.ts +32 -0
- package/app/server/live/LiveRoomChat.ts +285 -0
- package/app/server/live/LiveUpload.ts +81 -0
- package/app/server/routes/index.ts +3 -1
- package/app/server/routes/room.routes.ts +117 -0
- package/app/server/routes/users.routes.ts +35 -27
- package/app/shared/types/index.ts +14 -2
- package/config/app.config.ts +2 -62
- package/config/client.config.ts +2 -95
- package/config/database.config.ts +2 -99
- package/config/fluxstack.config.ts +25 -45
- package/config/index.ts +57 -38
- package/config/monitoring.config.ts +2 -114
- package/config/plugins.config.ts +2 -80
- package/config/server.config.ts +2 -68
- package/config/services.config.ts +2 -130
- package/config/system/app.config.ts +29 -0
- package/config/system/build.config.ts +49 -0
- package/config/system/client.config.ts +68 -0
- package/config/system/database.config.ts +17 -0
- package/config/system/fluxstack.config.ts +114 -0
- package/config/{logger.config.ts → system/logger.config.ts} +3 -1
- package/config/system/monitoring.config.ts +114 -0
- package/config/system/plugins.config.ts +84 -0
- package/config/{runtime.config.ts → system/runtime.config.ts} +1 -1
- package/config/system/server.config.ts +68 -0
- package/config/system/services.config.ts +46 -0
- package/config/{system.config.ts → system/system.config.ts} +1 -1
- package/core/build/flux-plugins-generator.ts +325 -325
- package/core/build/index.ts +39 -27
- package/core/build/live-components-generator.ts +3 -3
- package/core/build/optimizer.ts +235 -235
- package/core/cli/command-registry.ts +6 -4
- package/core/cli/commands/build.ts +79 -0
- package/core/cli/commands/create.ts +54 -0
- package/core/cli/commands/dev.ts +101 -0
- package/core/cli/commands/help.ts +34 -0
- package/core/cli/commands/index.ts +34 -0
- package/core/cli/commands/make-plugin.ts +90 -0
- package/core/cli/commands/plugin-add.ts +197 -0
- package/core/cli/commands/plugin-deps.ts +2 -2
- package/core/cli/commands/plugin-list.ts +208 -0
- package/core/cli/commands/plugin-remove.ts +170 -0
- package/core/cli/generators/component.ts +769 -769
- package/core/cli/generators/controller.ts +1 -1
- package/core/cli/generators/index.ts +146 -146
- package/core/cli/generators/interactive.ts +227 -227
- package/core/cli/generators/plugin.ts +2 -2
- package/core/cli/generators/prompts.ts +82 -82
- package/core/cli/generators/route.ts +6 -6
- package/core/cli/generators/service.ts +2 -2
- package/core/cli/generators/template-engine.ts +4 -3
- package/core/cli/generators/types.ts +2 -2
- package/core/cli/generators/utils.ts +191 -191
- package/core/cli/index.ts +115 -686
- package/core/cli/plugin-discovery.ts +2 -2
- package/core/client/LiveComponentsProvider.tsx +60 -8
- package/core/client/api/eden.ts +183 -0
- package/core/client/api/index.ts +11 -0
- package/core/client/components/Live.tsx +104 -0
- package/core/client/fluxstack.ts +1 -9
- package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
- package/core/client/hooks/state-validator.ts +1 -1
- package/core/client/hooks/useAuth.ts +48 -48
- package/core/client/hooks/useChunkedUpload.ts +85 -35
- package/core/client/hooks/useLiveChunkedUpload.ts +87 -0
- package/core/client/hooks/useLiveComponent.ts +800 -0
- package/core/client/hooks/useLiveUpload.ts +71 -0
- package/core/client/hooks/useRoom.ts +409 -0
- package/core/client/hooks/useRoomProxy.ts +382 -0
- package/core/client/index.ts +17 -68
- package/core/client/standalone-entry.ts +8 -0
- package/core/client/standalone.ts +74 -53
- package/core/client/state/createStore.ts +192 -192
- package/core/client/state/index.ts +14 -14
- package/core/config/index.ts +70 -291
- package/core/config/schema.ts +42 -723
- package/core/framework/client.ts +131 -131
- package/core/framework/index.ts +7 -7
- package/core/framework/server.ts +47 -40
- package/core/framework/types.ts +2 -2
- package/core/index.ts +23 -4
- package/core/live/ComponentRegistry.ts +3 -3
- package/core/live/types.ts +77 -0
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +242 -1066
- package/core/plugins/built-in/live-components/index.ts +1 -1
- package/core/plugins/built-in/monitoring/index.ts +111 -47
- package/core/plugins/built-in/static/index.ts +1 -1
- package/core/plugins/built-in/swagger/index.ts +68 -265
- package/core/plugins/built-in/vite/index.ts +85 -185
- package/core/plugins/built-in/vite/vite-dev.ts +10 -16
- package/core/plugins/config.ts +9 -7
- package/core/plugins/dependency-manager.ts +31 -1
- package/core/plugins/discovery.ts +19 -7
- package/core/plugins/executor.ts +2 -2
- package/core/plugins/index.ts +203 -203
- package/core/plugins/manager.ts +27 -39
- package/core/plugins/module-resolver.ts +19 -8
- package/core/plugins/registry.ts +255 -19
- package/core/plugins/types.ts +20 -53
- package/core/server/framework.ts +66 -43
- package/core/server/index.ts +15 -15
- package/core/server/live/ComponentRegistry.ts +78 -71
- package/core/server/live/FileUploadManager.ts +23 -10
- package/core/server/live/LiveComponentPerformanceMonitor.ts +1 -1
- package/core/server/live/LiveRoomManager.ts +261 -0
- package/core/server/live/RoomEventBus.ts +234 -0
- package/core/server/live/RoomStateManager.ts +172 -0
- package/core/server/live/StateSignature.ts +643 -643
- package/core/server/live/WebSocketConnectionManager.ts +30 -19
- package/core/server/live/auto-generated-components.ts +21 -9
- package/core/server/live/index.ts +14 -0
- package/core/server/live/websocket-plugin.ts +214 -67
- package/core/server/middleware/elysia-helpers.ts +7 -2
- package/core/server/middleware/errorHandling.ts +1 -1
- package/core/server/middleware/index.ts +31 -31
- package/core/server/plugins/database.ts +180 -180
- package/core/server/plugins/static-files-plugin.ts +69 -69
- package/core/server/plugins/swagger.ts +1 -1
- package/core/server/rooms/RoomBroadcaster.ts +357 -0
- package/core/server/rooms/RoomSystem.ts +463 -0
- package/core/server/rooms/index.ts +13 -0
- package/core/server/services/BaseService.ts +1 -1
- package/core/server/services/ServiceContainer.ts +1 -1
- package/core/server/services/index.ts +8 -8
- package/core/templates/create-project.ts +12 -12
- package/core/testing/index.ts +9 -9
- package/core/testing/setup.ts +73 -73
- package/core/types/api.ts +168 -168
- package/core/types/build.ts +219 -219
- package/core/types/config.ts +56 -26
- package/core/types/index.ts +4 -4
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +353 -14
- package/core/utils/build-logger.ts +324 -324
- package/core/utils/config-schema.ts +480 -480
- package/core/utils/env.ts +2 -8
- package/core/utils/errors/codes.ts +114 -114
- package/core/utils/errors/handlers.ts +36 -1
- package/core/utils/errors/index.ts +49 -5
- package/core/utils/errors/middleware.ts +113 -113
- package/core/utils/helpers.ts +6 -16
- package/core/utils/index.ts +17 -17
- package/core/utils/logger/colors.ts +114 -114
- package/core/utils/logger/config.ts +13 -9
- package/core/utils/logger/formatter.ts +82 -82
- package/core/utils/logger/group-logger.ts +101 -101
- package/core/utils/logger/index.ts +6 -1
- package/core/utils/logger/stack-trace.ts +3 -1
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/logger/winston-logger.ts +152 -152
- package/core/utils/monitoring/index.ts +211 -211
- package/core/utils/sync-version.ts +66 -66
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +8 -7
- package/package.json +12 -13
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +1 -1
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +302 -302
- package/plugins/crypto-auth/client/components/index.ts +11 -11
- package/plugins/crypto-auth/client/index.ts +11 -11
- package/plugins/crypto-auth/config/index.ts +1 -1
- package/plugins/crypto-auth/index.ts +4 -4
- package/plugins/crypto-auth/package.json +65 -65
- package/plugins/crypto-auth/server/AuthMiddleware.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthService.ts +185 -185
- package/plugins/crypto-auth/server/index.ts +21 -21
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +3 -3
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +2 -2
- package/plugins/crypto-auth/server/middlewares/helpers.ts +1 -1
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -22
- package/tsconfig.api-strict.json +16 -0
- package/tsconfig.json +48 -52
- package/{app/client/tsconfig.node.json → tsconfig.node.json} +25 -25
- package/types/global.d.ts +29 -29
- package/types/vitest.d.ts +8 -8
- package/vite.config.ts +38 -62
- package/vitest.config.live.ts +10 -9
- package/vitest.config.ts +29 -17
- package/app/client/README.md +0 -69
- package/app/client/SIMPLIFICATION.md +0 -140
- package/app/client/frontend-only.ts +0 -12
- package/app/client/src/live/FileUploadExample.tsx +0 -359
- package/app/client/src/live/MinimalLiveClock.tsx +0 -47
- package/app/client/src/live/QuickUploadTest.tsx +0 -193
- package/app/client/tsconfig.app.json +0 -45
- package/app/client/tsconfig.json +0 -7
- package/app/client/zustand-setup.md +0 -65
- package/app/server/backend-only.ts +0 -18
- package/app/server/live/LiveClockComponent.ts +0 -215
- package/app/server/live/LiveFileUploadComponent.ts +0 -77
- package/app/server/routes/env-test.ts +0 -110
- package/core/client/hooks/index.ts +0 -7
- package/core/client/hooks/useHybridLiveComponent.ts +0 -685
- package/core/client/hooks/useTypedLiveComponent.ts +0 -133
- package/core/client/hooks/useWebSocket.ts +0 -361
- package/core/config/env.ts +0 -546
- package/core/config/loader.ts +0 -522
- package/core/config/runtime-config.ts +0 -327
- package/core/config/validator.ts +0 -540
- package/core/server/backend-entry.ts +0 -51
- package/core/server/standalone.ts +0 -106
- package/core/utils/regenerate-files.ts +0 -69
- package/fluxstack.config.ts +0 -354
package/core/config/validator.ts
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration Validation System for FluxStack
|
|
3
|
-
* Provides comprehensive validation with detailed error reporting
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { FluxStackConfig } from './schema'
|
|
7
|
-
import { fluxStackConfigSchema } from './schema'
|
|
8
|
-
|
|
9
|
-
export interface ValidationError {
|
|
10
|
-
path: string
|
|
11
|
-
message: string
|
|
12
|
-
value?: any
|
|
13
|
-
expected?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ValidationWarning {
|
|
17
|
-
path: string
|
|
18
|
-
message: string
|
|
19
|
-
suggestion?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ValidationResult {
|
|
23
|
-
valid: boolean
|
|
24
|
-
errors: string[]
|
|
25
|
-
warnings: string[]
|
|
26
|
-
details: {
|
|
27
|
-
errors: ValidationError[]
|
|
28
|
-
warnings: ValidationWarning[]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* JSON Schema validator implementation
|
|
34
|
-
*/
|
|
35
|
-
class SchemaValidator {
|
|
36
|
-
private validateProperty(
|
|
37
|
-
value: any,
|
|
38
|
-
schema: any,
|
|
39
|
-
path: string = '',
|
|
40
|
-
errors: ValidationError[] = [],
|
|
41
|
-
warnings: ValidationWarning[] = []
|
|
42
|
-
): void {
|
|
43
|
-
if (schema.type) {
|
|
44
|
-
this.validateType(value, schema, path, errors)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (schema.properties && typeof value === 'object' && value !== null) {
|
|
48
|
-
this.validateObject(value, schema, path, errors, warnings)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (schema.items && Array.isArray(value)) {
|
|
52
|
-
this.validateArray(value, schema, path, errors, warnings)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (schema.enum) {
|
|
56
|
-
this.validateEnum(value, schema, path, errors)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (schema.pattern && typeof value === 'string') {
|
|
60
|
-
this.validatePattern(value, schema, path, errors)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (schema.minimum !== undefined && typeof value === 'number') {
|
|
64
|
-
this.validateMinimum(value, schema, path, errors)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (schema.maximum !== undefined && typeof value === 'number') {
|
|
68
|
-
this.validateMaximum(value, schema, path, errors)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (schema.minLength !== undefined && typeof value === 'string') {
|
|
72
|
-
this.validateMinLength(value, schema, path, errors)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (schema.maxLength !== undefined && typeof value === 'string') {
|
|
76
|
-
this.validateMaxLength(value, schema, path, errors)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (schema.minItems !== undefined && Array.isArray(value)) {
|
|
80
|
-
this.validateMinItems(value, schema, path, errors)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private validateType(value: any, schema: any, path: string, errors: ValidationError[]): void {
|
|
85
|
-
const actualType = Array.isArray(value) ? 'array' : typeof value
|
|
86
|
-
const expectedType = schema.type
|
|
87
|
-
|
|
88
|
-
if (actualType !== expectedType) {
|
|
89
|
-
errors.push({
|
|
90
|
-
path,
|
|
91
|
-
message: `Expected ${expectedType}, got ${actualType}`,
|
|
92
|
-
value,
|
|
93
|
-
expected: expectedType
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private validateObject(
|
|
99
|
-
value: any,
|
|
100
|
-
schema: any,
|
|
101
|
-
path: string,
|
|
102
|
-
errors: ValidationError[],
|
|
103
|
-
warnings: ValidationWarning[]
|
|
104
|
-
): void {
|
|
105
|
-
// Check required properties
|
|
106
|
-
if (schema.required) {
|
|
107
|
-
for (const requiredProp of schema.required) {
|
|
108
|
-
if (!(requiredProp in value)) {
|
|
109
|
-
errors.push({
|
|
110
|
-
path: path ? `${path}.${requiredProp}` : requiredProp,
|
|
111
|
-
message: `Missing required property '${requiredProp}'`,
|
|
112
|
-
expected: 'required property'
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Validate existing properties
|
|
119
|
-
for (const [key, propValue] of Object.entries(value)) {
|
|
120
|
-
const propPath = path ? `${path}.${key}` : key
|
|
121
|
-
const propSchema = schema.properties?.[key]
|
|
122
|
-
|
|
123
|
-
if (propSchema) {
|
|
124
|
-
this.validateProperty(propValue, propSchema, propPath, errors, warnings)
|
|
125
|
-
} else if (schema.additionalProperties === false) {
|
|
126
|
-
warnings.push({
|
|
127
|
-
path: propPath,
|
|
128
|
-
message: `Unknown property '${key}'`,
|
|
129
|
-
suggestion: 'Remove this property or add it to the schema'
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private validateArray(
|
|
136
|
-
value: any[],
|
|
137
|
-
schema: any,
|
|
138
|
-
path: string,
|
|
139
|
-
errors: ValidationError[],
|
|
140
|
-
warnings: ValidationWarning[]
|
|
141
|
-
): void {
|
|
142
|
-
value.forEach((item, index) => {
|
|
143
|
-
const itemPath = `${path}[${index}]`
|
|
144
|
-
this.validateProperty(item, schema.items, itemPath, errors, warnings)
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private validateEnum(value: any, schema: any, path: string, errors: ValidationError[]): void {
|
|
149
|
-
if (!schema.enum.includes(value)) {
|
|
150
|
-
errors.push({
|
|
151
|
-
path,
|
|
152
|
-
message: `Value must be one of: ${schema.enum.join(', ')}`,
|
|
153
|
-
value,
|
|
154
|
-
expected: schema.enum.join(' | ')
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private validatePattern(value: string, schema: any, path: string, errors: ValidationError[]): void {
|
|
160
|
-
const regex = new RegExp(schema.pattern)
|
|
161
|
-
if (!regex.test(value)) {
|
|
162
|
-
errors.push({
|
|
163
|
-
path,
|
|
164
|
-
message: `Value does not match pattern: ${schema.pattern}`,
|
|
165
|
-
value,
|
|
166
|
-
expected: `pattern: ${schema.pattern}`
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private validateMinimum(value: number, schema: any, path: string, errors: ValidationError[]): void {
|
|
172
|
-
if (value < schema.minimum) {
|
|
173
|
-
errors.push({
|
|
174
|
-
path,
|
|
175
|
-
message: `Value must be >= ${schema.minimum}`,
|
|
176
|
-
value,
|
|
177
|
-
expected: `>= ${schema.minimum}`
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private validateMaximum(value: number, schema: any, path: string, errors: ValidationError[]): void {
|
|
183
|
-
if (value > schema.maximum) {
|
|
184
|
-
errors.push({
|
|
185
|
-
path,
|
|
186
|
-
message: `Value must be <= ${schema.maximum}`,
|
|
187
|
-
value,
|
|
188
|
-
expected: `<= ${schema.maximum}`
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private validateMinLength(value: string, schema: any, path: string, errors: ValidationError[]): void {
|
|
194
|
-
if (value.length < schema.minLength) {
|
|
195
|
-
errors.push({
|
|
196
|
-
path,
|
|
197
|
-
message: `String must be at least ${schema.minLength} characters long`,
|
|
198
|
-
value,
|
|
199
|
-
expected: `length >= ${schema.minLength}`
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private validateMaxLength(value: string, schema: any, path: string, errors: ValidationError[]): void {
|
|
205
|
-
if (value.length > schema.maxLength) {
|
|
206
|
-
errors.push({
|
|
207
|
-
path,
|
|
208
|
-
message: `String must be at most ${schema.maxLength} characters long`,
|
|
209
|
-
value,
|
|
210
|
-
expected: `length <= ${schema.maxLength}`
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private validateMinItems(value: any[], schema: any, path: string, errors: ValidationError[]): void {
|
|
216
|
-
if (value.length < schema.minItems) {
|
|
217
|
-
errors.push({
|
|
218
|
-
path,
|
|
219
|
-
message: `Array must have at least ${schema.minItems} items`,
|
|
220
|
-
value,
|
|
221
|
-
expected: `length >= ${schema.minItems}`
|
|
222
|
-
})
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
validate(value: any, schema: any): ValidationResult {
|
|
227
|
-
const errors: ValidationError[] = []
|
|
228
|
-
const warnings: ValidationWarning[] = []
|
|
229
|
-
|
|
230
|
-
this.validateProperty(value, schema, '', errors, warnings)
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
valid: errors.length === 0,
|
|
234
|
-
errors: errors.map(e => `${e.path}: ${e.message}`),
|
|
235
|
-
warnings: warnings.map(w => `${w.path}: ${w.message}${w.suggestion ? ` (${w.suggestion})` : ''}`),
|
|
236
|
-
details: { errors, warnings }
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Business logic validation rules
|
|
243
|
-
*/
|
|
244
|
-
class BusinessValidator {
|
|
245
|
-
validate(config: FluxStackConfig): ValidationResult {
|
|
246
|
-
const errors: ValidationError[] = []
|
|
247
|
-
const warnings: ValidationWarning[] = []
|
|
248
|
-
|
|
249
|
-
// Port conflict validation
|
|
250
|
-
this.validatePortConflicts(config, errors)
|
|
251
|
-
|
|
252
|
-
// CORS validation
|
|
253
|
-
this.validateCorsConfiguration(config, warnings)
|
|
254
|
-
|
|
255
|
-
// Plugin validation
|
|
256
|
-
this.validatePluginConfiguration(config, warnings)
|
|
257
|
-
|
|
258
|
-
// Build configuration validation
|
|
259
|
-
this.validateBuildConfiguration(config, warnings)
|
|
260
|
-
|
|
261
|
-
// Environment-specific validation
|
|
262
|
-
this.validateEnvironmentConfiguration(config, warnings)
|
|
263
|
-
|
|
264
|
-
// Security validation
|
|
265
|
-
this.validateSecurityConfiguration(config, warnings)
|
|
266
|
-
|
|
267
|
-
return {
|
|
268
|
-
valid: errors.length === 0,
|
|
269
|
-
errors: errors.map(e => `${e.path}: ${e.message}`),
|
|
270
|
-
warnings: warnings.map(w => `${w.path}: ${w.message}${w.suggestion ? ` (${w.suggestion})` : ''}`),
|
|
271
|
-
details: { errors, warnings }
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private validatePortConflicts(config: FluxStackConfig, errors: ValidationError[]): void {
|
|
276
|
-
const ports = [config.server.port, config.client.port]
|
|
277
|
-
const uniquePorts = new Set(ports.filter(p => p !== 0)) // 0 means random port
|
|
278
|
-
|
|
279
|
-
if (uniquePorts.size !== ports.filter(p => p !== 0).length) {
|
|
280
|
-
errors.push({
|
|
281
|
-
path: 'ports',
|
|
282
|
-
message: 'Server and client ports must be different',
|
|
283
|
-
value: { server: config.server.port, client: config.client.port }
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private validateCorsConfiguration(config: FluxStackConfig, warnings: ValidationWarning[]): void {
|
|
289
|
-
const { cors } = config.server
|
|
290
|
-
|
|
291
|
-
// Check for overly permissive CORS
|
|
292
|
-
if (cors.origins.includes('*')) {
|
|
293
|
-
warnings.push({
|
|
294
|
-
path: 'server.cors.origins',
|
|
295
|
-
message: 'Using wildcard (*) for CORS origins is not recommended in production',
|
|
296
|
-
suggestion: 'Specify explicit origins for better security'
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check for missing common headers
|
|
301
|
-
const commonHeaders = ['Content-Type', 'Authorization']
|
|
302
|
-
const missingHeaders = commonHeaders.filter(h => !cors.headers.includes(h))
|
|
303
|
-
|
|
304
|
-
if (missingHeaders.length > 0) {
|
|
305
|
-
warnings.push({
|
|
306
|
-
path: 'server.cors.headers',
|
|
307
|
-
message: `Consider adding common headers: ${missingHeaders.join(', ')}`,
|
|
308
|
-
suggestion: 'These headers are commonly needed for API requests'
|
|
309
|
-
})
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private validatePluginConfiguration(config: FluxStackConfig, warnings: ValidationWarning[]): void {
|
|
314
|
-
const { enabled, disabled } = config.plugins
|
|
315
|
-
|
|
316
|
-
// Check for plugins in both enabled and disabled lists
|
|
317
|
-
const conflicts = enabled.filter(p => disabled.includes(p))
|
|
318
|
-
if (conflicts.length > 0) {
|
|
319
|
-
warnings.push({
|
|
320
|
-
path: 'plugins',
|
|
321
|
-
message: `Plugins listed in both enabled and disabled: ${conflicts.join(', ')}`,
|
|
322
|
-
suggestion: 'Remove from one of the lists'
|
|
323
|
-
})
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Check for essential plugins
|
|
327
|
-
const essentialPlugins = ['logger', 'cors']
|
|
328
|
-
const missingEssential = essentialPlugins.filter(p =>
|
|
329
|
-
!enabled.includes(p) || disabled.includes(p)
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
if (missingEssential.length > 0) {
|
|
333
|
-
warnings.push({
|
|
334
|
-
path: 'plugins.enabled',
|
|
335
|
-
message: `Consider enabling essential plugins: ${missingEssential.join(', ')}`,
|
|
336
|
-
suggestion: 'These plugins provide important functionality'
|
|
337
|
-
})
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
private validateBuildConfiguration(config: FluxStackConfig, warnings: ValidationWarning[]): void {
|
|
342
|
-
const { build } = config
|
|
343
|
-
|
|
344
|
-
// Check for development settings in production
|
|
345
|
-
if (process.env.NODE_ENV === 'production') {
|
|
346
|
-
if (!build.optimization.minify) {
|
|
347
|
-
warnings.push({
|
|
348
|
-
path: 'build.optimization.minify',
|
|
349
|
-
message: 'Minification is disabled in production',
|
|
350
|
-
suggestion: 'Enable minification for better performance'
|
|
351
|
-
})
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (!build.optimization.treeshake) {
|
|
355
|
-
warnings.push({
|
|
356
|
-
path: 'build.optimization.treeshake',
|
|
357
|
-
message: 'Tree-shaking is disabled in production',
|
|
358
|
-
suggestion: 'Enable tree-shaking to reduce bundle size'
|
|
359
|
-
})
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Check for conflicting settings
|
|
364
|
-
if (build.optimization.bundleAnalyzer && process.env.NODE_ENV === 'production') {
|
|
365
|
-
warnings.push({
|
|
366
|
-
path: 'build.optimization.bundleAnalyzer',
|
|
367
|
-
message: 'Bundle analyzer is enabled in production',
|
|
368
|
-
suggestion: 'Disable bundle analyzer in production builds'
|
|
369
|
-
})
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private validateEnvironmentConfiguration(config: FluxStackConfig, warnings: ValidationWarning[]): void {
|
|
374
|
-
if (config.environments) {
|
|
375
|
-
for (const [env, envConfig] of Object.entries(config.environments)) {
|
|
376
|
-
if (envConfig && typeof envConfig === 'object') {
|
|
377
|
-
// Check for potentially dangerous overrides
|
|
378
|
-
if ('server' in envConfig && envConfig.server && 'port' in envConfig.server) {
|
|
379
|
-
if (envConfig.server.port === 0 && env !== 'test') {
|
|
380
|
-
warnings.push({
|
|
381
|
-
path: `environments.${env}.server.port`,
|
|
382
|
-
message: 'Using random port (0) in non-test environment',
|
|
383
|
-
suggestion: 'Specify a fixed port for predictable deployments'
|
|
384
|
-
})
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
private validateSecurityConfiguration(config: FluxStackConfig, warnings: ValidationWarning[]): void {
|
|
393
|
-
// Check for missing authentication configuration in production
|
|
394
|
-
if (process.env.NODE_ENV === 'production' && !config.auth?.secret) {
|
|
395
|
-
warnings.push({
|
|
396
|
-
path: 'auth.secret',
|
|
397
|
-
message: 'No authentication secret configured for production',
|
|
398
|
-
suggestion: 'Set JWT_SECRET environment variable for secure authentication'
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Check for weak authentication settings
|
|
403
|
-
if (config.auth?.secret && config.auth.secret.length < 32) {
|
|
404
|
-
warnings.push({
|
|
405
|
-
path: 'auth.secret',
|
|
406
|
-
message: 'Authentication secret is too short',
|
|
407
|
-
suggestion: 'Use at least 32 characters for better security'
|
|
408
|
-
})
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Check for insecure CORS in production
|
|
412
|
-
if (process.env.NODE_ENV === 'production' && config.server.cors.credentials) {
|
|
413
|
-
const hasWildcard = config.server.cors.origins.includes('*')
|
|
414
|
-
if (hasWildcard) {
|
|
415
|
-
warnings.push({
|
|
416
|
-
path: 'server.cors',
|
|
417
|
-
message: 'CORS credentials enabled with wildcard origins in production',
|
|
418
|
-
suggestion: 'Specify explicit origins when using credentials'
|
|
419
|
-
})
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Main configuration validator
|
|
427
|
-
*/
|
|
428
|
-
export function validateConfig(config: FluxStackConfig): ValidationResult {
|
|
429
|
-
const schemaValidator = new SchemaValidator()
|
|
430
|
-
const businessValidator = new BusinessValidator()
|
|
431
|
-
|
|
432
|
-
// Validate against JSON schema
|
|
433
|
-
const schemaResult = schemaValidator.validate(config, fluxStackConfigSchema)
|
|
434
|
-
|
|
435
|
-
// Validate business rules
|
|
436
|
-
const businessResult = businessValidator.validate(config)
|
|
437
|
-
|
|
438
|
-
// Combine results
|
|
439
|
-
return {
|
|
440
|
-
valid: schemaResult.valid && businessResult.valid,
|
|
441
|
-
errors: [...schemaResult.errors, ...businessResult.errors],
|
|
442
|
-
warnings: [...schemaResult.warnings, ...businessResult.warnings],
|
|
443
|
-
details: {
|
|
444
|
-
errors: [...schemaResult.details.errors, ...businessResult.details.errors],
|
|
445
|
-
warnings: [...schemaResult.details.warnings, ...businessResult.details.warnings]
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Validate configuration and throw on errors
|
|
452
|
-
*/
|
|
453
|
-
export function validateConfigStrict(config: FluxStackConfig): void {
|
|
454
|
-
const result = validateConfig(config)
|
|
455
|
-
|
|
456
|
-
if (!result.valid) {
|
|
457
|
-
const errorMessage = [
|
|
458
|
-
'Configuration validation failed:',
|
|
459
|
-
...result.errors.map(e => ` - ${e}`),
|
|
460
|
-
...(result.warnings.length > 0 ? ['Warnings:', ...result.warnings.map(w => ` - ${w}`)] : [])
|
|
461
|
-
].join('\n')
|
|
462
|
-
|
|
463
|
-
throw new Error(errorMessage)
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Create a configuration validator for a specific environment
|
|
469
|
-
*/
|
|
470
|
-
export function createEnvironmentValidator(environment: string) {
|
|
471
|
-
return (config: FluxStackConfig): ValidationResult => {
|
|
472
|
-
// Apply environment-specific validation rules
|
|
473
|
-
const result = validateConfig(config)
|
|
474
|
-
|
|
475
|
-
// Add environment-specific warnings/errors
|
|
476
|
-
if (environment === 'production') {
|
|
477
|
-
// Additional production validations
|
|
478
|
-
if (config.logging.level === 'debug') {
|
|
479
|
-
result.warnings.push('Debug logging enabled in production - consider using "warn" or "error"')
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (!config.monitoring.enabled) {
|
|
483
|
-
result.warnings.push('Monitoring is disabled in production - consider enabling for better observability')
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (environment === 'development') {
|
|
488
|
-
// Additional development validations
|
|
489
|
-
if (config.build.optimization.minify) {
|
|
490
|
-
result.warnings.push('Minification enabled in development - this may slow down builds')
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return result
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* Validate partial configuration (useful for updates)
|
|
500
|
-
*/
|
|
501
|
-
export function validatePartialConfig(
|
|
502
|
-
partialConfig: Partial<FluxStackConfig>,
|
|
503
|
-
baseConfig: FluxStackConfig
|
|
504
|
-
): ValidationResult {
|
|
505
|
-
// Merge partial config with base config
|
|
506
|
-
const mergedConfig = { ...baseConfig, ...partialConfig }
|
|
507
|
-
|
|
508
|
-
// Validate the merged configuration
|
|
509
|
-
return validateConfig(mergedConfig)
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Get validation suggestions for improving configuration
|
|
514
|
-
*/
|
|
515
|
-
export function getConfigSuggestions(config: FluxStackConfig): string[] {
|
|
516
|
-
const result = validateConfig(config)
|
|
517
|
-
const suggestions: string[] = []
|
|
518
|
-
|
|
519
|
-
// Extract suggestions from warnings
|
|
520
|
-
for (const warning of result.details.warnings) {
|
|
521
|
-
if (warning.suggestion) {
|
|
522
|
-
suggestions.push(`${warning.path}: ${warning.suggestion}`)
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Add general suggestions based on configuration
|
|
527
|
-
if (!config.monitoring.enabled) {
|
|
528
|
-
suggestions.push('Consider enabling monitoring for better observability')
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (config.plugins.enabled.length === 0) {
|
|
532
|
-
suggestions.push('Consider enabling some plugins to extend functionality')
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (!config.database && !config.custom?.database) {
|
|
536
|
-
suggestions.push('Consider adding database configuration if your app needs persistence')
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return suggestions
|
|
540
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// Standalone backend server (sem frontend integrado)
|
|
2
|
-
import { FluxStackFramework } from "./index"
|
|
3
|
-
import type { Plugin, PluginContext } from "../types"
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Helper to safely parse request.url which might be relative or absolute
|
|
7
|
-
*/
|
|
8
|
-
function parseRequestURL(request: Request): URL {
|
|
9
|
-
try {
|
|
10
|
-
// Try parsing as absolute URL first
|
|
11
|
-
return new URL(request.url)
|
|
12
|
-
} catch {
|
|
13
|
-
// If relative, use host from headers or default to localhost
|
|
14
|
-
const host = request.headers.get('host') || 'localhost'
|
|
15
|
-
const protocol = request.headers.get('x-forwarded-proto') || 'http'
|
|
16
|
-
return new URL(request.url, `${protocol}://${host}`)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const createStandaloneServer = (userConfig: any = {}) => {
|
|
21
|
-
const app = new FluxStackFramework({
|
|
22
|
-
server: {
|
|
23
|
-
port: userConfig.port || parseInt(process.env.BACKEND_PORT || '3000'),
|
|
24
|
-
host: 'localhost',
|
|
25
|
-
apiPrefix: userConfig.apiPrefix || "/api",
|
|
26
|
-
cors: {
|
|
27
|
-
origins: ['*'],
|
|
28
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
29
|
-
headers: ['Content-Type', 'Authorization'],
|
|
30
|
-
credentials: false,
|
|
31
|
-
maxAge: 86400
|
|
32
|
-
},
|
|
33
|
-
middleware: []
|
|
34
|
-
},
|
|
35
|
-
app: { name: 'FluxStack Backend', version: '1.7.4' },
|
|
36
|
-
client: { port: 5173, proxy: { target: 'http://localhost:3000' }, build: { sourceMaps: true, minify: false, target: 'es2020', outDir: 'dist' } },
|
|
37
|
-
...userConfig
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// Plugin de logging silencioso para standalone
|
|
41
|
-
const silentLogger: Plugin = {
|
|
42
|
-
name: "silent-logger",
|
|
43
|
-
setup: (context: PluginContext) => {
|
|
44
|
-
context.app.onRequest(({ request }: { request: Request }) => {
|
|
45
|
-
// Log mais limpo para backend standalone
|
|
46
|
-
const timestamp = new Date().toLocaleTimeString()
|
|
47
|
-
const path = parseRequestURL(request).pathname
|
|
48
|
-
console.log(`[${timestamp}] ${request.method} ${path}`)
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
app.use(silentLogger)
|
|
54
|
-
return app
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const startBackendOnly = async (userRoutes?: any, config: any = {}) => {
|
|
58
|
-
const port = config.port || process.env.BACKEND_PORT || 3000
|
|
59
|
-
const host = process.env.HOST || 'localhost'
|
|
60
|
-
|
|
61
|
-
console.log(`🦊 FluxStack Backend`)
|
|
62
|
-
console.log(`🚀 http://${host}:${port}`)
|
|
63
|
-
console.log(`📋 Health: http://${host}:${port}/health`)
|
|
64
|
-
console.log()
|
|
65
|
-
|
|
66
|
-
const app = createStandaloneServer(config)
|
|
67
|
-
|
|
68
|
-
if (userRoutes) {
|
|
69
|
-
app.routes(userRoutes)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Adicionar rotas básicas para backend standalone
|
|
73
|
-
const framework = app.getApp()
|
|
74
|
-
|
|
75
|
-
// Health check
|
|
76
|
-
framework.get("/health", () => ({
|
|
77
|
-
status: "ok",
|
|
78
|
-
mode: "backend-only",
|
|
79
|
-
timestamp: new Date().toISOString(),
|
|
80
|
-
port
|
|
81
|
-
}))
|
|
82
|
-
|
|
83
|
-
// Rota raiz informativa para backend standalone
|
|
84
|
-
framework.get("/", () => ({
|
|
85
|
-
message: "🦊 FluxStack Backend Server",
|
|
86
|
-
mode: "backend-only",
|
|
87
|
-
endpoints: {
|
|
88
|
-
health: "/health",
|
|
89
|
-
api: "/api/*",
|
|
90
|
-
docs: "/swagger"
|
|
91
|
-
},
|
|
92
|
-
frontend: {
|
|
93
|
-
note: "Frontend não está rodando neste servidor",
|
|
94
|
-
recommendation: "Use 'bun run dev' para modo integrado ou 'bun run dev:frontend' para frontend standalone"
|
|
95
|
-
},
|
|
96
|
-
timestamp: new Date().toISOString()
|
|
97
|
-
}))
|
|
98
|
-
|
|
99
|
-
// Override do listen para não mostrar mensagens do framework
|
|
100
|
-
const context = app.getContext()
|
|
101
|
-
framework.listen(context.config.port!, () => {
|
|
102
|
-
// Mensagem já foi mostrada acima, não repetir
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
return app
|
|
106
|
-
}
|