create-fluxstack 1.13.0 → 1.15.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/LLMD/patterns/anti-patterns.md +100 -0
- package/LLMD/reference/routing.md +39 -39
- package/LLMD/resources/live-auth.md +20 -2
- package/LLMD/resources/live-components.md +300 -21
- package/LLMD/resources/live-logging.md +95 -33
- package/LLMD/resources/live-upload.md +59 -8
- package/app/client/.live-stubs/LiveAdminPanel.js +5 -0
- package/app/client/.live-stubs/LiveChat.js +7 -0
- package/app/client/.live-stubs/LiveCounter.js +9 -0
- package/app/client/.live-stubs/LiveForm.js +11 -0
- package/app/client/.live-stubs/LiveLocalCounter.js +8 -0
- package/app/client/.live-stubs/LiveRoomChat.js +10 -0
- package/app/client/.live-stubs/LiveTodoList.js +9 -0
- package/app/client/.live-stubs/LiveUpload.js +15 -0
- package/app/client/index.html +2 -2
- package/app/client/public/favicon.svg +46 -0
- package/app/client/src/App.tsx +13 -1
- package/app/client/src/assets/fluxstack-static.svg +46 -0
- package/app/client/src/assets/fluxstack.svg +183 -0
- package/app/client/src/components/AppLayout.tsx +146 -9
- package/app/client/src/components/BackButton.tsx +13 -13
- package/app/client/src/components/DemoPage.tsx +4 -4
- package/app/client/src/live/AuthDemo.tsx +23 -21
- package/app/client/src/live/ChatDemo.tsx +2 -2
- package/app/client/src/live/CounterDemo.tsx +12 -12
- package/app/client/src/live/FormDemo.tsx +2 -2
- package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
- package/app/client/src/live/RoomChatDemo.tsx +24 -16
- package/app/client/src/live/TodoListDemo.tsx +158 -0
- package/app/client/src/main.tsx +13 -13
- package/app/client/src/pages/ApiTestPage.tsx +6 -6
- package/app/client/src/pages/HomePage.tsx +80 -52
- package/app/server/auth/DevAuthProvider.ts +2 -2
- package/app/server/auth/JWTAuthProvider.example.ts +2 -2
- package/app/server/index.ts +2 -2
- package/app/server/live/LiveAdminPanel.ts +2 -1
- package/app/server/live/LiveChat.ts +78 -77
- package/app/server/live/LiveCounter.ts +1 -1
- package/app/server/live/LiveForm.ts +1 -0
- package/app/server/live/LiveLocalCounter.ts +38 -37
- package/app/server/live/LiveProtectedChat.ts +2 -1
- package/app/server/live/LiveRoomChat.ts +1 -0
- package/app/server/live/LiveTodoList.ts +110 -0
- package/app/server/live/LiveUpload.ts +1 -0
- package/app/server/live/register-components.ts +19 -19
- package/app/server/routes/room.routes.ts +1 -2
- package/config/system/runtime.config.ts +4 -0
- package/core/build/live-components-generator.ts +1 -1
- package/core/build/optimizer.ts +235 -235
- package/core/build/vite-plugins.ts +28 -0
- package/core/client/components/LiveDebugger.tsx +1324 -0
- package/core/client/hooks/useLiveUpload.ts +3 -4
- package/core/client/index.ts +41 -21
- package/core/framework/server.ts +1 -1
- package/core/plugins/built-in/index.ts +134 -134
- package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
- package/core/plugins/built-in/vite/index.ts +75 -21
- package/core/server/index.ts +14 -15
- package/core/server/live/auto-generated-components.ts +6 -3
- package/core/server/live/index.ts +95 -21
- package/core/server/live/websocket-plugin.ts +27 -862
- package/core/server/plugins/static-files-plugin.ts +179 -69
- package/core/types/build.ts +219 -219
- package/core/types/plugin.ts +107 -107
- package/core/types/types.ts +77 -890
- package/core/utils/logger/startup-banner.ts +82 -82
- package/core/utils/version.ts +6 -6
- package/create-fluxstack.ts +1 -1
- package/package.json +5 -1
- package/plugins/crypto-auth/index.ts +1 -1
- package/plugins/crypto-auth/server/CryptoAuthLiveProvider.ts +2 -2
- package/vite.config.ts +40 -12
- package/app/client/src/assets/react.svg +0 -1
- package/core/client/LiveComponentsProvider.tsx +0 -531
- package/core/client/components/Live.tsx +0 -105
- package/core/client/hooks/AdaptiveChunkSizer.ts +0 -215
- package/core/client/hooks/state-validator.ts +0 -130
- package/core/client/hooks/useChunkedUpload.ts +0 -359
- package/core/client/hooks/useLiveChunkedUpload.ts +0 -87
- package/core/client/hooks/useLiveComponent.ts +0 -843
- package/core/client/hooks/useRoom.ts +0 -409
- package/core/client/hooks/useRoomProxy.ts +0 -382
- package/core/server/live/ComponentRegistry.ts +0 -1099
- package/core/server/live/FileUploadManager.ts +0 -282
- package/core/server/live/LiveComponentPerformanceMonitor.ts +0 -931
- package/core/server/live/LiveLogger.ts +0 -111
- package/core/server/live/LiveRoomManager.ts +0 -262
- package/core/server/live/RoomEventBus.ts +0 -234
- package/core/server/live/RoomStateManager.ts +0 -172
- package/core/server/live/SingleConnectionManager.ts +0 -0
- package/core/server/live/StateSignature.ts +0 -645
- package/core/server/live/WebSocketConnectionManager.ts +0 -709
- package/core/server/live/auth/LiveAuthContext.ts +0 -71
- package/core/server/live/auth/LiveAuthManager.ts +0 -304
- package/core/server/live/auth/index.ts +0 -19
- package/core/server/live/auth/types.ts +0 -179
|
@@ -1,69 +1,179 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
1
|
+
// FluxStack Static Files Plugin - Serve Public Files & Uploads
|
|
2
|
+
|
|
3
|
+
import { mkdir } from 'fs/promises'
|
|
4
|
+
import { resolve, extname, basename } from 'path'
|
|
5
|
+
import type { Plugin, PluginContext } from '../../plugins/types'
|
|
6
|
+
import { pluginsConfig } from '@config'
|
|
7
|
+
|
|
8
|
+
/** MIME types that should force a download instead of rendering inline */
|
|
9
|
+
const DANGEROUS_MIME_TYPES = new Set([
|
|
10
|
+
'application/x-msdownload',
|
|
11
|
+
'application/x-executable',
|
|
12
|
+
'application/x-sharedlib',
|
|
13
|
+
'application/x-mach-binary',
|
|
14
|
+
'application/x-dosexec',
|
|
15
|
+
'application/x-httpd-php',
|
|
16
|
+
'application/java-archive',
|
|
17
|
+
'application/x-sh',
|
|
18
|
+
'application/x-csh',
|
|
19
|
+
'application/x-bat',
|
|
20
|
+
])
|
|
21
|
+
|
|
22
|
+
/** File extensions that should always force a download */
|
|
23
|
+
const DANGEROUS_EXTENSIONS = new Set([
|
|
24
|
+
'.exe', '.dll', '.bat', '.cmd', '.com', '.msi',
|
|
25
|
+
'.sh', '.csh', '.bash', '.ps1', '.vbs', '.wsf',
|
|
26
|
+
'.php', '.jsp', '.asp', '.aspx', '.py', '.rb', '.pl',
|
|
27
|
+
'.jar', '.war', '.class',
|
|
28
|
+
'.scr', '.pif', '.hta',
|
|
29
|
+
'.svg', // SVG can contain embedded scripts
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
/** Extensions that carry a content hash in their filename (immutable) */
|
|
33
|
+
const HASHED_EXT = /\.[0-9a-f]{8,}\.\w+$/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate a weak ETag from Bun.file() metadata.
|
|
37
|
+
* Uses file.lastModified (ms timestamp) which is available without reading the file.
|
|
38
|
+
* Size comes from stat() since BunFile.size is unreliable until contents are read.
|
|
39
|
+
*/
|
|
40
|
+
function generateETag(size: number, lastModified: number): string {
|
|
41
|
+
return `W/"${size.toString(16)}-${lastModified.toString(16)}"`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Sanitize a filename for use in Content-Disposition header */
|
|
45
|
+
function sanitizeFilename(name: string): string {
|
|
46
|
+
return name.replace(/[/\\:\0\x01-\x1f\x7f]/g, '_').replace(/"/g, '\\"')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Check if a MIME type or extension should force download */
|
|
50
|
+
function shouldForceDownload(filePath: string, mimeType: string | undefined): boolean {
|
|
51
|
+
const ext = extname(filePath).toLowerCase()
|
|
52
|
+
if (DANGEROUS_EXTENSIONS.has(ext)) return true
|
|
53
|
+
if (mimeType && DANGEROUS_MIME_TYPES.has(mimeType)) return true
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const staticFilesPlugin: Plugin = {
|
|
58
|
+
name: 'static-files',
|
|
59
|
+
description: 'Serve static files and uploads',
|
|
60
|
+
author: 'FluxStack Team',
|
|
61
|
+
priority: 'normal',
|
|
62
|
+
category: 'core',
|
|
63
|
+
tags: ['static', 'files', 'uploads'],
|
|
64
|
+
|
|
65
|
+
setup: async (context: PluginContext) => {
|
|
66
|
+
if (!pluginsConfig.staticFilesEnabled) {
|
|
67
|
+
context.logger.debug('Static files plugin disabled')
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const projectRoot = process.cwd()
|
|
72
|
+
const publicDir = resolve(projectRoot, pluginsConfig.staticPublicDir ?? 'public')
|
|
73
|
+
const uploadsDir = resolve(projectRoot, pluginsConfig.staticUploadsDir ?? 'uploads')
|
|
74
|
+
const cacheMaxAge = pluginsConfig.staticCacheMaxAge
|
|
75
|
+
const enablePublic = pluginsConfig.staticEnablePublic
|
|
76
|
+
const enableUploads = pluginsConfig.staticEnableUploads
|
|
77
|
+
|
|
78
|
+
// Async handler — uses Bun.file() APIs instead of Node fs
|
|
79
|
+
const serveFile = (baseDir: string, isUpload: boolean) => async ({ params, set, request }: any) => {
|
|
80
|
+
const requestedPath: string = params['*'] || ''
|
|
81
|
+
|
|
82
|
+
// Reject null bytes early — prevents filesystem confusion
|
|
83
|
+
if (requestedPath.includes('\0')) {
|
|
84
|
+
set.status = 400
|
|
85
|
+
return { error: 'Invalid path' }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const filePath = resolve(baseDir, requestedPath)
|
|
89
|
+
|
|
90
|
+
// Path traversal protection
|
|
91
|
+
if (!filePath.startsWith(baseDir)) {
|
|
92
|
+
set.status = 400
|
|
93
|
+
return { error: 'Invalid path' }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const file = Bun.file(filePath)
|
|
97
|
+
|
|
98
|
+
// Bun.file().stat() — single async call, no Node fs import needed.
|
|
99
|
+
// Returns size, isFile(), ctime etc. without reading file contents.
|
|
100
|
+
let stat: Awaited<ReturnType<typeof file.stat>>
|
|
101
|
+
try {
|
|
102
|
+
stat = await file.stat()
|
|
103
|
+
if (!stat.isFile()) {
|
|
104
|
+
set.status = 404
|
|
105
|
+
return { error: 'Not a file' }
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
set.status = 404
|
|
109
|
+
return { error: 'File not found' }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ETag from stat.size (reliable) + file.lastModified (Bun-native, no extra syscall)
|
|
113
|
+
const etag = generateETag(stat.size, file.lastModified)
|
|
114
|
+
const lastModified = new Date(file.lastModified).toUTCString()
|
|
115
|
+
|
|
116
|
+
// Conditional request: If-None-Match takes priority over If-Modified-Since
|
|
117
|
+
const ifNoneMatch = request?.headers?.get?.('if-none-match')
|
|
118
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
119
|
+
set.status = 304
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ifModifiedSince = request?.headers?.get?.('if-modified-since')
|
|
124
|
+
if (!ifNoneMatch && ifModifiedSince) {
|
|
125
|
+
const clientDate = new Date(ifModifiedSince).getTime()
|
|
126
|
+
if (!isNaN(clientDate) && file.lastModified <= clientDate) {
|
|
127
|
+
set.status = 304
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Security headers
|
|
133
|
+
set.headers['x-content-type-options'] = 'nosniff'
|
|
134
|
+
set.headers['etag'] = etag
|
|
135
|
+
set.headers['last-modified'] = lastModified
|
|
136
|
+
|
|
137
|
+
// Cache strategy: hashed assets are immutable, uploads get short cache
|
|
138
|
+
if (!isUpload && HASHED_EXT.test(requestedPath)) {
|
|
139
|
+
set.headers['cache-control'] = `public, max-age=${cacheMaxAge}, immutable`
|
|
140
|
+
} else if (isUpload) {
|
|
141
|
+
set.headers['cache-control'] = 'public, max-age=3600, must-revalidate'
|
|
142
|
+
} else {
|
|
143
|
+
set.headers['cache-control'] = `public, max-age=${cacheMaxAge}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Force download for dangerous MIME types
|
|
147
|
+
// file.type is resolved from extension by Bun — no disk I/O
|
|
148
|
+
if (shouldForceDownload(filePath, file.type)) {
|
|
149
|
+
const fileName = sanitizeFilename(basename(requestedPath) || 'download')
|
|
150
|
+
set.headers['content-disposition'] = `attachment; filename="${fileName}"`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Returning Bun.file() directly lets Bun use sendfile(2) for zero-copy transfer
|
|
154
|
+
return file
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Register routes based on config flags
|
|
158
|
+
if (enablePublic) {
|
|
159
|
+
await mkdir(publicDir, { recursive: true })
|
|
160
|
+
context.app.get('/api/static/*', serveFile(publicDir, false))
|
|
161
|
+
context.logger.debug('Static public files route registered: /api/static/*')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (enableUploads) {
|
|
165
|
+
await mkdir(uploadsDir, { recursive: true })
|
|
166
|
+
context.app.get('/api/uploads/*', serveFile(uploadsDir, true))
|
|
167
|
+
context.logger.debug('Static uploads route registered: /api/uploads/*')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const routes = [
|
|
171
|
+
...(enablePublic ? ['/api/static/*'] : []),
|
|
172
|
+
...(enableUploads ? ['/api/uploads/*'] : [])
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
if (routes.length > 0) {
|
|
176
|
+
context.logger.debug('Static files plugin ready', { routes })
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|