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.
- package/core/build/bundler.ts +169 -0
- package/core/build/index.ts +386 -0
- package/core/build/optimizer.ts +268 -0
- package/core/templates/create-project.ts +11 -41
- package/eslint.config.js +23 -0
- package/package.json +1 -1
- package/tailwind.config.js +34 -0
- package/vitest.config.live.ts +68 -0
- package/vitest.config.ts +42 -0
- package/workspace.json +6 -0
|
@@ -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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
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() {
|
package/eslint.config.js
ADDED
|
@@ -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
|
@@ -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
|
+
})
|
package/vitest.config.ts
ADDED
|
@@ -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
|
+
})
|