create-fluxstack 1.0.14 → 1.0.16

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
+ }
@@ -34,15 +34,15 @@ export class ProjectCreator {
34
34
  // 3. Generate package.json
35
35
  await this.generatePackageJson()
36
36
 
37
- // 4. Generate config files
37
+ // 4. Generate config files (including .gitignore)
38
38
  await this.generateConfigFiles()
39
39
 
40
- // 5. Install dependencies
41
- await this.installDependencies()
42
-
43
- // 6. Initialize git
40
+ // 5. Initialize git (before installing dependencies)
44
41
  await this.initGit()
45
42
 
43
+ // 6. Install dependencies (last step)
44
+ await this.installDependencies()
45
+
46
46
  console.log()
47
47
  console.log("🎉 Project created successfully!")
48
48
  console.log()
@@ -330,6 +330,124 @@ BUILD_OUTDIR=dist
330
330
 
331
331
  await Bun.write(join(this.targetDir, ".env"), envContent)
332
332
 
333
+ // .gitignore
334
+ const gitignoreContent = `# Dependencies
335
+ node_modules/
336
+ .pnp
337
+ .pnp.js
338
+
339
+ # Production builds
340
+ /dist
341
+ /build
342
+ /.next/
343
+ /out/
344
+
345
+ # Environment variables
346
+ .env
347
+ .env.local
348
+ .env.development.local
349
+ .env.test.local
350
+ .env.production.local
351
+
352
+ # Logs
353
+ npm-debug.log*
354
+ yarn-debug.log*
355
+ yarn-error.log*
356
+ lerna-debug.log*
357
+ .pnpm-debug.log*
358
+
359
+ # Runtime data
360
+ pids
361
+ *.pid
362
+ *.seed
363
+ *.pid.lock
364
+
365
+ # Coverage directory used by tools like istanbul
366
+ coverage/
367
+ *.lcov
368
+
369
+ # nyc test coverage
370
+ .nyc_output
371
+
372
+ # Dependency directories
373
+ jspm_packages/
374
+
375
+ # TypeScript cache
376
+ *.tsbuildinfo
377
+
378
+ # Optional npm cache directory
379
+ .npm
380
+
381
+ # Optional eslint cache
382
+ .eslintcache
383
+
384
+ # Optional stylelint cache
385
+ .stylelintcache
386
+
387
+ # Microbundle cache
388
+ .rpt2_cache/
389
+ .rts2_cache_cjs/
390
+ .rts2_cache_es/
391
+ .rts2_cache_umd/
392
+
393
+ # Optional REPL history
394
+ .node_repl_history
395
+
396
+ # Output of 'npm pack'
397
+ *.tgz
398
+
399
+ # Yarn Integrity file
400
+ .yarn-integrity
401
+
402
+ # parcel-bundler cache (https://parceljs.org/)
403
+ .cache
404
+ .parcel-cache
405
+
406
+ # Next.js build output
407
+ .next
408
+
409
+ # Nuxt.js build / generate output
410
+ .nuxt
411
+ dist
412
+
413
+ # Storybook build outputs
414
+ .out
415
+ .storybook-out
416
+
417
+ # Temporary folders
418
+ tmp/
419
+ temp/
420
+
421
+ # Editor directories and files
422
+ .vscode/*
423
+ !.vscode/extensions.json
424
+ .idea
425
+ *.suo
426
+ *.ntvs*
427
+ *.njsproj
428
+ *.sln
429
+ *.sw?
430
+
431
+ # OS generated files
432
+ .DS_Store
433
+ .DS_Store?
434
+ ._*
435
+ .Spotlight-V100
436
+ .Trashes
437
+ ehthumbs.db
438
+ Thumbs.db
439
+
440
+ # FluxStack specific
441
+ uploads/
442
+ public/uploads/
443
+ .fluxstack/
444
+
445
+ # Bun
446
+ bun.lockb
447
+ `
448
+
449
+ await Bun.write(join(this.targetDir, ".gitignore"), gitignoreContent)
450
+
333
451
  // README
334
452
  const readme = `# ${this.projectName}
335
453
 
@@ -469,31 +587,32 @@ bun.lockb
469
587
  private async initGit() {
470
588
  console.log("🔧 Initializing git repository...")
471
589
 
472
- const gitInitProcess = spawn({
473
- cmd: ["git", "init"],
474
- cwd: this.targetDir,
475
- stdout: "pipe",
476
- stderr: "pipe"
477
- })
478
-
479
- await gitInitProcess.exited
480
-
481
- const gitAddProcess = spawn({
482
- cmd: ["git", "add", "."],
483
- cwd: this.targetDir,
484
- stdout: "pipe",
485
- stderr: "pipe"
486
- })
487
-
488
- await gitAddProcess.exited
489
-
490
- const gitCommitProcess = spawn({
491
- cmd: ["git", "commit", "-m", "Initial commit - FluxStack project created"],
492
- cwd: this.targetDir,
493
- stdout: "pipe",
494
- stderr: "pipe"
495
- })
496
-
497
- await gitCommitProcess.exited
590
+ try {
591
+ // Initialize git repository
592
+ await spawn({
593
+ cmd: ["git", "init", "--quiet"],
594
+ cwd: this.targetDir,
595
+ stdout: "ignore",
596
+ stderr: "ignore"
597
+ }).exited
598
+
599
+ // Add all files
600
+ await spawn({
601
+ cmd: ["git", "add", "."],
602
+ cwd: this.targetDir,
603
+ stdout: "ignore",
604
+ stderr: "ignore"
605
+ }).exited
606
+
607
+ // Initial commit
608
+ await spawn({
609
+ cmd: ["git", "commit", "-m", "Initial commit - FluxStack project created", "--quiet"],
610
+ cwd: this.targetDir,
611
+ stdout: "ignore",
612
+ stderr: "ignore"
613
+ }).exited
614
+ } catch (error) {
615
+ console.warn("⚠️ Git initialization failed (git may not be installed)")
616
+ }
498
617
  }
499
618
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-fluxstack",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "⚡ Modern full-stack TypeScript framework with Elysia + React + Bun",
5
5
  "keywords": [
6
6
  "framework",