create-fluxstack 1.0.15 → 1.0.18

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.
@@ -0,0 +1,169 @@
1
+ import { spawn } from "bun"
2
+ import { existsSync, mkdirSync } from "fs"
3
+ import { join } from "path"
4
+ import type { FluxStackConfig } from "../config"
5
+ import type { BundleResult, BundleOptions } from "../types/build"
6
+
7
+ export interface BundlerConfig {
8
+ target: 'bun' | 'node' | 'docker'
9
+ outDir: string
10
+ sourceMaps: boolean
11
+ external?: string[]
12
+ minify?: boolean
13
+ }
14
+
15
+ export class Bundler {
16
+ private config: BundlerConfig
17
+
18
+ constructor(config: BundlerConfig) {
19
+ this.config = config
20
+ }
21
+
22
+ async bundleClient(options: BundleOptions = {}): Promise<BundleResult> {
23
+ console.log("⚡ Bundling client...")
24
+
25
+ const startTime = Date.now()
26
+
27
+ try {
28
+ const buildProcess = spawn({
29
+ cmd: ["bunx", "vite", "build", "--config", "vite.config.ts"],
30
+ cwd: process.cwd(),
31
+ stdout: "pipe",
32
+ stderr: "pipe",
33
+ env: {
34
+ ...process.env,
35
+ VITE_BUILD_OUTDIR: this.config.outDir,
36
+ VITE_BUILD_MINIFY: (this.config.minify || false).toString(),
37
+ VITE_BUILD_SOURCEMAPS: this.config.sourceMaps.toString(),
38
+ ...options.env
39
+ }
40
+ })
41
+
42
+ const exitCode = await buildProcess.exited
43
+ const duration = Date.now() - startTime
44
+
45
+ if (exitCode === 0) {
46
+ console.log("✅ Client bundle completed")
47
+ return {
48
+ success: true,
49
+ duration,
50
+ outputPath: this.config.outDir,
51
+ assets: await this.getClientAssets()
52
+ }
53
+ } else {
54
+ const stderr = await new Response(buildProcess.stderr).text()
55
+ console.error("❌ Client bundle failed")
56
+ return {
57
+ success: false,
58
+ duration,
59
+ error: stderr || "Client build failed"
60
+ }
61
+ }
62
+ } catch (error) {
63
+ const duration = Date.now() - startTime
64
+ return {
65
+ success: false,
66
+ duration,
67
+ error: error instanceof Error ? error.message : "Unknown error"
68
+ }
69
+ }
70
+ }
71
+
72
+ async bundleServer(entryPoint: string, options: BundleOptions = {}): Promise<BundleResult> {
73
+ console.log("⚡ Bundling server...")
74
+
75
+ const startTime = Date.now()
76
+
77
+ try {
78
+ // Ensure output directory exists
79
+ if (!existsSync(this.config.outDir)) {
80
+ mkdirSync(this.config.outDir, { recursive: true })
81
+ }
82
+
83
+ const external = [
84
+ "@tailwindcss/vite",
85
+ "tailwindcss",
86
+ "lightningcss",
87
+ "vite",
88
+ "@vitejs/plugin-react",
89
+ ...(this.config.external || []),
90
+ ...(options.external || [])
91
+ ]
92
+
93
+ const buildArgs = [
94
+ "bun", "build",
95
+ entryPoint,
96
+ "--outdir", this.config.outDir,
97
+ "--target", this.config.target,
98
+ ...external.flatMap(ext => ["--external", ext])
99
+ ]
100
+
101
+ if (this.config.sourceMaps) {
102
+ buildArgs.push("--sourcemap")
103
+ }
104
+
105
+ if (this.config.minify) {
106
+ buildArgs.push("--minify")
107
+ }
108
+
109
+ const buildProcess = spawn({
110
+ cmd: buildArgs,
111
+ stdout: "pipe",
112
+ stderr: "pipe",
113
+ env: {
114
+ ...process.env,
115
+ ...options.env
116
+ }
117
+ })
118
+
119
+ const exitCode = await buildProcess.exited
120
+ const duration = Date.now() - startTime
121
+
122
+ if (exitCode === 0) {
123
+ console.log("✅ Server bundle completed")
124
+ return {
125
+ success: true,
126
+ duration,
127
+ outputPath: this.config.outDir,
128
+ entryPoint: join(this.config.outDir, "index.js")
129
+ }
130
+ } else {
131
+ const stderr = await new Response(buildProcess.stderr).text()
132
+ console.error("❌ Server bundle failed")
133
+ return {
134
+ success: false,
135
+ duration,
136
+ error: stderr || "Server build failed"
137
+ }
138
+ }
139
+ } catch (error) {
140
+ const duration = Date.now() - startTime
141
+ return {
142
+ success: false,
143
+ duration,
144
+ error: error instanceof Error ? error.message : "Unknown error"
145
+ }
146
+ }
147
+ }
148
+
149
+ private async getClientAssets(): Promise<string[]> {
150
+ // This would analyze the build output to get asset information
151
+ // For now, return empty array - can be enhanced later
152
+ return []
153
+ }
154
+
155
+ async bundle(clientEntry?: string, serverEntry?: string, options: BundleOptions = {}): Promise<{
156
+ client: BundleResult
157
+ server: BundleResult
158
+ }> {
159
+ const [clientResult, serverResult] = await Promise.all([
160
+ clientEntry ? this.bundleClient(options) : Promise.resolve({ success: true, duration: 0 } as BundleResult),
161
+ serverEntry ? this.bundleServer(serverEntry, options) : Promise.resolve({ success: true, duration: 0 } as BundleResult)
162
+ ])
163
+
164
+ return {
165
+ client: clientResult,
166
+ server: serverResult
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,386 @@
1
+ import { copyFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
2
+ import { join } from "path"
3
+ import type { FluxStackConfig } from "../config"
4
+ import type { BuildResult, BuildManifest } from "../types/build"
5
+ import { Bundler } from "./bundler"
6
+ import { Optimizer } from "./optimizer"
7
+
8
+ export class FluxStackBuilder {
9
+ private config: FluxStackConfig
10
+ private bundler: Bundler
11
+ private optimizer: Optimizer
12
+
13
+ constructor(config: FluxStackConfig) {
14
+ this.config = config
15
+
16
+ // Initialize bundler with configuration
17
+ this.bundler = new Bundler({
18
+ target: config.build.target,
19
+ outDir: config.build.outDir,
20
+ sourceMaps: config.build.sourceMaps,
21
+ minify: config.build.minify,
22
+ external: config.build.external
23
+ })
24
+
25
+ // Initialize optimizer with configuration
26
+ this.optimizer = new Optimizer({
27
+ minify: config.build.minify,
28
+ treeshake: config.build.treeshake,
29
+ compress: config.build.compress || false,
30
+ removeUnusedCSS: config.build.removeUnusedCSS || false,
31
+ optimizeImages: config.build.optimizeImages || false,
32
+ bundleAnalysis: config.build.bundleAnalysis || false
33
+ })
34
+ }
35
+
36
+ async buildClient() {
37
+ return await this.bundler.bundleClient({
38
+ env: {
39
+ VITE_BUILD_OUTDIR: this.config.client.build.outDir,
40
+ VITE_BUILD_MINIFY: this.config.client.build.minify.toString(),
41
+ VITE_BUILD_SOURCEMAPS: this.config.client.build.sourceMaps.toString()
42
+ }
43
+ })
44
+ }
45
+
46
+ async buildServer() {
47
+ return await this.bundler.bundleServer("app/server/index.ts")
48
+ }
49
+
50
+ async createDockerFiles() {
51
+ console.log("🐳 Creating Docker files...")
52
+
53
+ const distDir = this.config.build.outDir
54
+ console.log(`📁 Output directory: ${distDir}`)
55
+
56
+ // Ensure dist directory exists
57
+ if (!existsSync(distDir)) {
58
+ console.log(`📁 Creating directory: ${distDir}`)
59
+ mkdirSync(distDir, { recursive: true })
60
+ console.log(`✅ Directory created successfully`)
61
+ } else {
62
+ console.log(`✅ Directory already exists`)
63
+ }
64
+
65
+ // Dockerfile optimizado para produção
66
+ const dockerfile = `# FluxStack Production Docker Image
67
+ FROM oven/bun:1.1-alpine AS production
68
+
69
+ WORKDIR /app
70
+
71
+ # Copy package.json first for better caching
72
+ COPY package.json ./
73
+
74
+ # Install dependencies
75
+ RUN bun install --frozen-lockfile
76
+
77
+ # Copy built application
78
+ COPY . .
79
+
80
+ # Create non-root user
81
+ RUN addgroup -g 1001 -S fluxstack && \\
82
+ adduser -S fluxstack -u 1001
83
+
84
+ # Set permissions
85
+ RUN chown -R fluxstack:fluxstack /app
86
+ USER fluxstack
87
+
88
+ # Environment variables
89
+ ENV NODE_ENV=production
90
+ ENV PORT=3000
91
+
92
+ # Health check
93
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
94
+ CMD bun run -e "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1))" || exit 1
95
+
96
+ # Expose port
97
+ EXPOSE 3000
98
+
99
+ # Start the application
100
+ CMD ["bun", "run", "index.js"]
101
+ `
102
+
103
+ // docker-compose.yml para deploy rápido
104
+ const dockerCompose = `version: '3.8'
105
+
106
+ services:
107
+ fluxstack:
108
+ build: .
109
+ ports:
110
+ - "3000:3000"
111
+ environment:
112
+ - NODE_ENV=production
113
+ - PORT=3000
114
+ restart: unless-stopped
115
+ healthcheck:
116
+ test: ["CMD", "bun", "run", "-e", "fetch('http://localhost:3000/api/health').then(r => r.ok ? process.exit(0) : process.exit(1))"]
117
+ interval: 30s
118
+ timeout: 3s
119
+ retries: 3
120
+ deploy:
121
+ resources:
122
+ limits:
123
+ memory: 512M
124
+ reservations:
125
+ memory: 256M
126
+
127
+ # Opcional: adicionar nginx reverse proxy
128
+ # nginx:
129
+ # image: nginx:alpine
130
+ # ports:
131
+ # - "80:80"
132
+ # volumes:
133
+ # - ./nginx.conf:/etc/nginx/nginx.conf
134
+ # depends_on:
135
+ # - fluxstack
136
+ # restart: unless-stopped
137
+ `
138
+
139
+ // .dockerignore otimizado
140
+ const dockerignore = `node_modules
141
+ .git
142
+ .gitignore
143
+ README.md
144
+ .env.local
145
+ .env.*.local
146
+ npm-debug.log*
147
+ yarn-debug.log*
148
+ yarn-error.log*
149
+ .DS_Store
150
+ *.log
151
+ coverage
152
+ .nyc_output
153
+ .vscode
154
+ .idea
155
+ *.swp
156
+ *.swo
157
+ `
158
+
159
+ // Escrever arquivos no dist
160
+ try {
161
+ console.log(`📝 Writing Dockerfile...`)
162
+ writeFileSync(join(distDir, "Dockerfile"), dockerfile)
163
+ console.log(`📝 Writing docker-compose.yml...`)
164
+ writeFileSync(join(distDir, "docker-compose.yml"), dockerCompose)
165
+ console.log(`📝 Writing .dockerignore...`)
166
+ writeFileSync(join(distDir, ".dockerignore"), dockerignore)
167
+ } catch (error) {
168
+ console.error(`❌ Error writing Docker files:`, error)
169
+ throw error
170
+ }
171
+
172
+ // Copiar .env ou criar um de exemplo
173
+ const envPath = join(process.cwd(), '.env')
174
+ const envExamplePath = join(process.cwd(), '.env.example')
175
+ const distEnvPath = join(distDir, ".env")
176
+
177
+ console.log(`🔍 Checking for .env files...`)
178
+ console.log(` - .env path: ${envPath}`)
179
+ console.log(` - .env.example path: ${envExamplePath}`)
180
+ console.log(` - target path: ${distEnvPath}`)
181
+
182
+ if (existsSync(envPath)) {
183
+ console.log(`📄 Copying .env file...`)
184
+ copyFileSync(envPath, distEnvPath)
185
+ console.log("📄 Environment file copied to dist/")
186
+ } else if (existsSync(envExamplePath)) {
187
+ console.log(`📄 Copying .env.example file...`)
188
+ copyFileSync(envExamplePath, distEnvPath)
189
+ console.log("📄 Example environment file copied to dist/")
190
+ } else {
191
+ console.log(`📄 Creating default .env file...`)
192
+ // Criar um .env básico para produção
193
+ const defaultEnv = `NODE_ENV=production
194
+ PORT=3000
195
+ FLUXSTACK_APP_NAME=fluxstack-app
196
+ FLUXSTACK_APP_VERSION=1.0.0
197
+ LOG_LEVEL=info
198
+ MONITORING_ENABLED=true
199
+ `
200
+ writeFileSync(distEnvPath, defaultEnv)
201
+ console.log("📄 Default environment file created for production")
202
+ }
203
+
204
+ // Copy package.json for Docker build
205
+ const packageJsonPath = join(process.cwd(), 'package.json')
206
+ const distPackageJsonPath = join(distDir, 'package.json')
207
+
208
+ console.log(`📦 Copying package.json...`)
209
+ console.log(` - source: ${packageJsonPath}`)
210
+ console.log(` - target: ${distPackageJsonPath}`)
211
+
212
+ if (existsSync(packageJsonPath)) {
213
+ copyFileSync(packageJsonPath, distPackageJsonPath)
214
+ console.log("📦 Package.json copied successfully")
215
+ } else {
216
+ console.warn("⚠️ package.json not found, creating minimal version...")
217
+ const minimalPackageJson = {
218
+ name: "fluxstack-app",
219
+ version: "1.0.0",
220
+ type: "module",
221
+ scripts: {
222
+ start: "bun run index.js"
223
+ },
224
+ dependencies: {}
225
+ }
226
+ writeFileSync(distPackageJsonPath, JSON.stringify(minimalPackageJson, null, 2))
227
+ }
228
+
229
+ console.log("✅ Docker files created in dist/")
230
+ }
231
+
232
+ async build(): Promise<BuildResult> {
233
+ console.log("⚡ FluxStack Framework - Building...")
234
+
235
+ const startTime = Date.now()
236
+
237
+ try {
238
+ // Validate configuration
239
+ await this.validateConfig()
240
+
241
+ // Clean output directory if requested
242
+ if (this.config.build.clean) {
243
+ await this.clean()
244
+ }
245
+
246
+ // Build client and server
247
+ const clientResult = await this.buildClient()
248
+ const serverResult = await this.buildServer()
249
+
250
+ // Check if builds were successful
251
+ if (!clientResult.success || !serverResult.success) {
252
+ return {
253
+ success: false,
254
+ duration: Date.now() - startTime,
255
+ error: clientResult.error || serverResult.error || "Build failed",
256
+ outputFiles: [],
257
+ warnings: [],
258
+ errors: [],
259
+ stats: {
260
+ totalSize: 0,
261
+ gzippedSize: 0,
262
+ chunkCount: 0,
263
+ assetCount: 0,
264
+ entryPoints: [],
265
+ dependencies: []
266
+ }
267
+ }
268
+ }
269
+
270
+ // Optimize build if enabled
271
+ let optimizationResult
272
+ if (this.config.build.optimize) {
273
+ optimizationResult = await this.optimizer.optimize(this.config.build.outDir)
274
+ }
275
+
276
+ // Create Docker files
277
+ await this.createDockerFiles()
278
+
279
+ // Generate build manifest
280
+ const manifest = await this.generateManifest(clientResult, serverResult, optimizationResult)
281
+
282
+ const duration = Date.now() - startTime
283
+
284
+ console.log("🎉 Build completed successfully!")
285
+ console.log(`⏱️ Build time: ${duration}ms`)
286
+ console.log("🐳 Ready for Docker deployment from dist/ directory")
287
+
288
+ return {
289
+ success: true,
290
+ duration,
291
+ outputFiles: [],
292
+ warnings: [],
293
+ errors: [],
294
+ stats: {
295
+ totalSize: optimizationResult?.optimizedSize || 0,
296
+ gzippedSize: 0,
297
+ chunkCount: 0,
298
+ assetCount: clientResult.assets?.length || 0,
299
+ entryPoints: [serverResult.entryPoint || ""].filter(Boolean),
300
+ dependencies: []
301
+ }
302
+ }
303
+
304
+ } catch (error) {
305
+ const duration = Date.now() - startTime
306
+ const errorMessage = error instanceof Error ? error.message : "Unknown build error"
307
+
308
+ console.error("❌ Build failed:", errorMessage)
309
+
310
+ return {
311
+ success: false,
312
+ duration,
313
+ error: errorMessage,
314
+ outputFiles: [],
315
+ warnings: [],
316
+ errors: [],
317
+ stats: {
318
+ totalSize: 0,
319
+ gzippedSize: 0,
320
+ chunkCount: 0,
321
+ assetCount: 0,
322
+ entryPoints: [],
323
+ dependencies: []
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ private async validateConfig(): Promise<void> {
330
+ // Validate build configuration
331
+ if (!this.config.build.outDir) {
332
+ throw new Error("Build output directory not specified")
333
+ }
334
+
335
+ if (!this.config.build.target) {
336
+ throw new Error("Build target not specified")
337
+ }
338
+ }
339
+
340
+ private async clean(): Promise<void> {
341
+ // Clean output directory - implementation would go here
342
+ console.log("🧹 Cleaning output directory...")
343
+ }
344
+
345
+ private async generateManifest(
346
+ clientResult: any,
347
+ serverResult: any,
348
+ optimizationResult?: any
349
+ ): Promise<BuildManifest> {
350
+ return {
351
+ version: this.config.app.version,
352
+ timestamp: new Date().toISOString(),
353
+ target: this.config.build.target,
354
+ mode: this.config.build.mode || 'production',
355
+ client: {
356
+ entryPoints: [],
357
+ chunks: [],
358
+ assets: clientResult.assets || [],
359
+ publicPath: '/'
360
+ },
361
+ server: {
362
+ entryPoint: serverResult.entryPoint || '',
363
+ dependencies: [],
364
+ externals: this.config.build.external || []
365
+ },
366
+ assets: [],
367
+ optimization: {
368
+ minified: this.config.build.minify,
369
+ treeshaken: this.config.build.treeshake,
370
+ compressed: this.config.build.compress || false,
371
+ originalSize: optimizationResult?.originalSize || 0,
372
+ optimizedSize: optimizationResult?.optimizedSize || 0,
373
+ compressionRatio: optimizationResult?.compressionRatio || 0
374
+ },
375
+ metrics: {
376
+ buildTime: clientResult.duration + serverResult.duration,
377
+ bundleTime: 0,
378
+ optimizationTime: optimizationResult?.duration || 0,
379
+ totalSize: optimizationResult?.optimizedSize || 0,
380
+ gzippedSize: 0,
381
+ chunkCount: 0,
382
+ assetCount: clientResult.assets?.length || 0
383
+ }
384
+ }
385
+ }
386
+ }
@@ -0,0 +1,268 @@
1
+ import { readFileSync, writeFileSync, statSync, readdirSync } from "fs"
2
+ import { join, extname } from "path"
3
+ import { gzipSync } from "zlib"
4
+ import type { OptimizationConfig, OptimizationResult } from "../types/build"
5
+
6
+ export interface OptimizerConfig {
7
+ minify: boolean
8
+ treeshake: boolean
9
+ compress: boolean
10
+ removeUnusedCSS: boolean
11
+ optimizeImages: boolean
12
+ bundleAnalysis: boolean
13
+ }
14
+
15
+ export class Optimizer {
16
+ private config: OptimizerConfig
17
+
18
+ constructor(config: OptimizerConfig) {
19
+ this.config = config
20
+ }
21
+
22
+ async optimize(buildPath: string): Promise<OptimizationResult> {
23
+ console.log("🔧 Optimizing build...")
24
+
25
+ const startTime = Date.now()
26
+ const results: OptimizationResult = {
27
+ success: true,
28
+ duration: 0,
29
+ originalSize: 0,
30
+ optimizedSize: 0,
31
+ compressionRatio: 0,
32
+ optimizations: []
33
+ }
34
+
35
+ try {
36
+ // Get original size
37
+ results.originalSize = await this.calculateDirectorySize(buildPath)
38
+
39
+ // Apply optimizations
40
+ if (this.config.minify) {
41
+ await this.minifyAssets(buildPath, results)
42
+ }
43
+
44
+ if (this.config.compress) {
45
+ await this.compressAssets(buildPath, results)
46
+ }
47
+
48
+ if (this.config.removeUnusedCSS) {
49
+ await this.removeUnusedCSS(buildPath, results)
50
+ }
51
+
52
+ if (this.config.optimizeImages) {
53
+ await this.optimizeImages(buildPath, results)
54
+ }
55
+
56
+ if (this.config.bundleAnalysis) {
57
+ await this.analyzeBundles(buildPath, results)
58
+ }
59
+
60
+ // Calculate final size and compression ratio
61
+ results.optimizedSize = await this.calculateDirectorySize(buildPath)
62
+ results.compressionRatio = results.originalSize > 0
63
+ ? ((results.originalSize - results.optimizedSize) / results.originalSize) * 100
64
+ : 0
65
+
66
+ results.duration = Date.now() - startTime
67
+
68
+ console.log(`✅ Optimization completed in ${results.duration}ms`)
69
+ console.log(`📊 Size reduction: ${results.compressionRatio.toFixed(2)}%`)
70
+
71
+ return results
72
+
73
+ } catch (error) {
74
+ results.success = false
75
+ results.duration = Date.now() - startTime
76
+ results.error = error instanceof Error ? error.message : "Unknown optimization error"
77
+
78
+ console.error("❌ Optimization failed:", results.error)
79
+ return results
80
+ }
81
+ }
82
+
83
+ private async minifyAssets(buildPath: string, results: OptimizationResult): Promise<void> {
84
+ console.log("🗜️ Minifying assets...")
85
+
86
+ const files = this.getFilesRecursively(buildPath)
87
+ let minifiedCount = 0
88
+
89
+ for (const file of files) {
90
+ const ext = extname(file).toLowerCase()
91
+
92
+ if (ext === '.js' || ext === '.css') {
93
+ try {
94
+ const content = readFileSync(file, 'utf-8')
95
+ const minified = await this.minifyContent(content, ext)
96
+
97
+ if (minified !== content) {
98
+ writeFileSync(file, minified)
99
+ minifiedCount++
100
+ }
101
+ } catch (error) {
102
+ console.warn(`⚠️ Failed to minify ${file}:`, error)
103
+ }
104
+ }
105
+ }
106
+
107
+ results.optimizations.push({
108
+ type: 'minification',
109
+ description: `Minified ${minifiedCount} files`,
110
+ sizeSaved: 0 // Would calculate actual size saved
111
+ })
112
+ }
113
+
114
+ private async minifyContent(content: string, type: string): Promise<string> {
115
+ // Basic minification - in a real implementation, you'd use proper minifiers
116
+ if (type === '.js') {
117
+ return content
118
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
119
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
120
+ .replace(/\s+/g, ' ') // Collapse whitespace
121
+ .trim()
122
+ } else if (type === '.css') {
123
+ return content
124
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove comments
125
+ .replace(/\s+/g, ' ') // Collapse whitespace
126
+ .replace(/;\s*}/g, '}') // Remove unnecessary semicolons
127
+ .trim()
128
+ }
129
+
130
+ return content
131
+ }
132
+
133
+ private async compressAssets(buildPath: string, results: OptimizationResult): Promise<void> {
134
+ console.log("📦 Compressing assets...")
135
+
136
+ const files = this.getFilesRecursively(buildPath)
137
+ let compressedCount = 0
138
+
139
+ for (const file of files) {
140
+ const ext = extname(file).toLowerCase()
141
+
142
+ if (['.js', '.css', '.html', '.json', '.svg'].includes(ext)) {
143
+ try {
144
+ const content = readFileSync(file)
145
+ const compressed = gzipSync(content)
146
+
147
+ // Only create .gz file if it's significantly smaller
148
+ if (compressed.length < content.length * 0.9) {
149
+ writeFileSync(file + '.gz', compressed)
150
+ compressedCount++
151
+ }
152
+ } catch (error) {
153
+ console.warn(`⚠️ Failed to compress ${file}:`, error)
154
+ }
155
+ }
156
+ }
157
+
158
+ results.optimizations.push({
159
+ type: 'compression',
160
+ description: `Created gzip versions for ${compressedCount} files`,
161
+ sizeSaved: 0
162
+ })
163
+ }
164
+
165
+ private async removeUnusedCSS(buildPath: string, results: OptimizationResult): Promise<void> {
166
+ console.log("🎨 Removing unused CSS...")
167
+
168
+ // This is a placeholder - real implementation would use PurgeCSS or similar
169
+ results.optimizations.push({
170
+ type: 'css-purging',
171
+ description: 'CSS purging not implemented yet',
172
+ sizeSaved: 0
173
+ })
174
+ }
175
+
176
+ private async optimizeImages(buildPath: string, results: OptimizationResult): Promise<void> {
177
+ console.log("🖼️ Optimizing images...")
178
+
179
+ // This is a placeholder - real implementation would use imagemin or similar
180
+ results.optimizations.push({
181
+ type: 'image-optimization',
182
+ description: 'Image optimization not implemented yet',
183
+ sizeSaved: 0
184
+ })
185
+ }
186
+
187
+ private async analyzeBundles(buildPath: string, results: OptimizationResult): Promise<void> {
188
+ console.log("📊 Analyzing bundles...")
189
+
190
+ const files = this.getFilesRecursively(buildPath)
191
+ const jsFiles = files.filter(f => extname(f) === '.js')
192
+
193
+ let totalJSSize = 0
194
+ for (const file of jsFiles) {
195
+ totalJSSize += statSync(file).size
196
+ }
197
+
198
+ results.optimizations.push({
199
+ type: 'bundle-analysis',
200
+ description: `Analyzed ${jsFiles.length} JS bundles (${(totalJSSize / 1024).toFixed(2)} KB total)`,
201
+ sizeSaved: 0
202
+ })
203
+ }
204
+
205
+ private async calculateDirectorySize(dirPath: string): Promise<number> {
206
+ const files = this.getFilesRecursively(dirPath)
207
+ let totalSize = 0
208
+
209
+ for (const file of files) {
210
+ try {
211
+ totalSize += statSync(file).size
212
+ } catch (error) {
213
+ // Ignore files that can't be read
214
+ }
215
+ }
216
+
217
+ return totalSize
218
+ }
219
+
220
+ private getFilesRecursively(dir: string): string[] {
221
+ const files: string[] = []
222
+
223
+ try {
224
+ const items = readdirSync(dir, { withFileTypes: true })
225
+
226
+ for (const item of items) {
227
+ const fullPath = join(dir, item.name)
228
+
229
+ if (item.isDirectory()) {
230
+ files.push(...this.getFilesRecursively(fullPath))
231
+ } else {
232
+ files.push(fullPath)
233
+ }
234
+ }
235
+ } catch (error) {
236
+ // Ignore directories that can't be read
237
+ }
238
+
239
+ return files
240
+ }
241
+
242
+ async createOptimizationReport(result: OptimizationResult): Promise<string> {
243
+ const report = `
244
+ # Build Optimization Report
245
+
246
+ ## Summary
247
+ - **Status**: ${result.success ? '✅ Success' : '❌ Failed'}
248
+ - **Duration**: ${result.duration}ms
249
+ - **Original Size**: ${(result.originalSize / 1024).toFixed(2)} KB
250
+ - **Optimized Size**: ${(result.optimizedSize / 1024).toFixed(2)} KB
251
+ - **Size Reduction**: ${result.compressionRatio.toFixed(2)}%
252
+
253
+ ## Optimizations Applied
254
+
255
+ ${result.optimizations.map(opt =>
256
+ `### ${opt.type.charAt(0).toUpperCase() + opt.type.slice(1)}
257
+ - ${opt.description}
258
+ - Size Saved: ${(opt.sizeSaved / 1024).toFixed(2)} KB`
259
+ ).join('\n\n')}
260
+
261
+ ${result.error ? `## Error\n${result.error}` : ''}
262
+
263
+ Generated at: ${new Date().toISOString()}
264
+ `
265
+
266
+ return report.trim()
267
+ }
268
+ }
@@ -151,6 +151,8 @@ export class ProjectCreator {
151
151
  "@types/bun": "latest",
152
152
  "@types/react": "^18.2.0",
153
153
  "@types/react-dom": "^18.2.0",
154
+ "@types/uuid": "^10.0.0",
155
+ "@types/ws": "^8.18.1",
154
156
  "@testing-library/react": "^14.0.0",
155
157
  "@testing-library/jest-dom": "^6.1.0",
156
158
  "@testing-library/user-event": "^14.5.0",
@@ -164,10 +166,16 @@ export class ProjectCreator {
164
166
  "@elysiajs/eden": "^1.3.2",
165
167
  "@sinclair/typebox": "^0.34.41",
166
168
  "@vitejs/plugin-react": "^4.0.0",
167
- elysia: "latest",
168
- react: "^18.2.0",
169
+ "chalk": "^5.3.0",
170
+ "chokidar": "^4.0.3",
171
+ "elysia": "latest",
172
+ "react": "^18.2.0",
169
173
  "react-dom": "^18.2.0",
170
- vite: "^5.0.0"
174
+ "react-icons": "^5.5.0",
175
+ "uuid": "^13.0.0",
176
+ "vite": "^5.0.0",
177
+ "ws": "^8.18.3",
178
+ "zustand": "^5.0.8"
171
179
  }
172
180
  }
173
181
 
@@ -527,44 +535,6 @@ Built with ❤️ using FluxStack framework.
527
535
  `
528
536
 
529
537
  await Bun.write(join(this.targetDir, "README.md"), readme)
530
-
531
- // .gitignore
532
- const gitignore = `# Dependencies
533
- node_modules/
534
- *.lockb
535
-
536
- # Build outputs
537
- dist/
538
- build/
539
- .next/
540
-
541
- # Environment variables
542
- .env.local
543
- .env.production
544
-
545
- # IDE
546
- .vscode/
547
- .idea/
548
- *.swp
549
- *.swo
550
-
551
- # OS
552
- .DS_Store
553
- Thumbs.db
554
-
555
- # Logs
556
- *.log
557
- logs/
558
-
559
- # Runtime
560
- .tmp/
561
- .cache/
562
-
563
- # Bun
564
- bun.lockb
565
- `
566
-
567
- await Bun.write(join(this.targetDir, ".gitignore"), gitignore)
568
538
  }
569
539
 
570
540
  private async installDependencies() {
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { globalIgnores } from 'eslint/config'
7
+
8
+ export default tseslint.config([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs['recommended-latest'],
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.0.15",
3
+ "version": "1.0.18",
4
4
  "description": "⚡ Modern full-stack TypeScript framework with Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,34 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./app/client/index.html",
5
+ "./app/client/src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ // Custom colors for FluxStack branding
11
+ flux: {
12
+ 50: '#f0f9ff',
13
+ 100: '#e0f2fe',
14
+ 200: '#bae6fd',
15
+ 300: '#7dd3fc',
16
+ 400: '#38bdf8',
17
+ 500: '#0ea5e9',
18
+ 600: '#0284c7',
19
+ 700: '#0369a1',
20
+ 800: '#075985',
21
+ 900: '#0c4a6e',
22
+ }
23
+ },
24
+ fontFamily: {
25
+ sans: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'sans-serif'],
26
+ },
27
+ animation: {
28
+ 'spin-slow': 'spin 3s linear infinite',
29
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
30
+ }
31
+ },
32
+ },
33
+ plugins: [],
34
+ }
@@ -0,0 +1,68 @@
1
+ // 🧪 Vitest Configuration for Live Components Tests
2
+
3
+ import { defineConfig } from 'vitest/config'
4
+ import path from 'path'
5
+
6
+ export default defineConfig({
7
+ test: {
8
+ name: 'live-components',
9
+ root: './core/server/live',
10
+ environment: 'node',
11
+ setupFiles: ['./__tests__/setup.ts'],
12
+ include: [
13
+ '**/__tests__/**/*.test.ts'
14
+ ],
15
+ exclude: [
16
+ '**/node_modules/**',
17
+ '**/dist/**',
18
+ '**/.{idea,git,cache,output,temp}/**'
19
+ ],
20
+ globals: true,
21
+ coverage: {
22
+ provider: 'v8',
23
+ reporter: ['text', 'json', 'html'],
24
+ reportsDirectory: './coverage/live-components',
25
+ include: [
26
+ 'core/server/live/**/*.ts'
27
+ ],
28
+ exclude: [
29
+ 'core/server/live/**/__tests__/**',
30
+ 'core/server/live/**/*.test.ts',
31
+ 'core/server/live/**/*.spec.ts'
32
+ ],
33
+ thresholds: {
34
+ global: {
35
+ branches: 80,
36
+ functions: 80,
37
+ lines: 80,
38
+ statements: 80
39
+ }
40
+ }
41
+ },
42
+ testTimeout: 10000,
43
+ hookTimeout: 10000,
44
+ teardownTimeout: 5000,
45
+ isolate: true,
46
+ pool: 'threads',
47
+ poolOptions: {
48
+ threads: {
49
+ singleThread: false,
50
+ minThreads: 1,
51
+ maxThreads: 4
52
+ }
53
+ },
54
+ reporter: ['verbose', 'json'],
55
+ outputFile: {
56
+ json: './test-results/live-components.json'
57
+ }
58
+ },
59
+ resolve: {
60
+ alias: {
61
+ '@': path.resolve(__dirname, './core'),
62
+ '@tests': path.resolve(__dirname, './core/server/live/__tests__')
63
+ }
64
+ },
65
+ esbuild: {
66
+ target: 'node18'
67
+ }
68
+ })
@@ -0,0 +1,42 @@
1
+ /// <reference types="vitest" />
2
+ /// <reference types="@testing-library/jest-dom" />
3
+ import { defineConfig } from 'vitest/config'
4
+ import react from '@vitejs/plugin-react'
5
+ import { resolve } from 'path'
6
+
7
+ export default defineConfig({
8
+ plugins: [react()],
9
+ test: {
10
+ globals: true,
11
+ environment: 'jsdom',
12
+ setupFiles: ['./app/client/src/test/setup.ts'],
13
+ include: [
14
+ 'app/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}',
15
+ 'core/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'
16
+ ],
17
+ exclude: [
18
+ 'node_modules',
19
+ 'dist',
20
+ '.git',
21
+ '.cache'
22
+ ],
23
+ coverage: {
24
+ provider: 'v8',
25
+ reporter: ['text', 'json', 'html'],
26
+ exclude: [
27
+ 'node_modules/',
28
+ 'app/client/src/test/',
29
+ '**/*.d.ts',
30
+ '**/*.config.*',
31
+ '**/coverage/**'
32
+ ]
33
+ }
34
+ },
35
+ resolve: {
36
+ alias: {
37
+ '@': resolve(__dirname, './app/client/src'),
38
+ '@core': resolve(__dirname, './core'),
39
+ '@server': resolve(__dirname, './app/server')
40
+ }
41
+ }
42
+ })
package/workspace.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "fluxstack-workspace",
3
+ "workspaces": [
4
+ "./packages/*"
5
+ ]
6
+ }