Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,7 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*


chrome_data
core/server/live/auto-generated-components.ts
254 changes: 199 additions & 55 deletions core/build/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,34 +79,18 @@ export class Bundler {
let liveComponentsGenerator: any = null

try {
// 🚀 PRE-BUILD: Auto-generate Live Components registration
const generatorModule = await import('./live-components-generator')
liveComponentsGenerator = generatorModule.liveComponentsGenerator
const discoveredComponents = await liveComponentsGenerator.preBuild()

// 🔌 PRE-BUILD: Auto-generate FluxStack Plugins registration
const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
const discoveredPlugins = await fluxPluginsGenerator.preBuild()

// Run pre-build steps
liveComponentsGenerator = await this.runPreBuildSteps()

// Ensure output directory exists
if (!existsSync(this.config.outDir)) {
mkdirSync(this.config.outDir, { recursive: true })
}
this.ensureOutputDirectory()

const external = [
"@tailwindcss/vite",
"tailwindcss",
"lightningcss",
"vite",
"@vitejs/plugin-react",
...(this.config.external || []),
...(options.external || [])
]
// Get external dependencies
const external = this.getExternalDependencies(options)

const buildArgs = [
"bun", "build",
entryPoint,
"bun", "build",
entryPoint,
"--outdir", this.config.outDir,
"--target", this.config.target,
...external.flatMap(ext => ["--external", ext])
Expand All @@ -132,20 +116,12 @@ export class Bundler {
const exitCode = await buildProcess.exited
const duration = Date.now() - startTime

// 🧹 POST-BUILD: Handle auto-generated registration file
// (liveComponentsGenerator already available from above)

if (exitCode === 0) {
buildLogger.success(`Server bundle completed in ${buildLogger.formatDuration(duration)}`)

// Keep generated files for production (they're now baked into bundle)
await liveComponentsGenerator.postBuild(false)

// Cleanup plugins registry
const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
await fluxPluginsGenerator.postBuild(false)


// Run post-build cleanup
await this.runPostBuildCleanup(liveComponentsGenerator)

return {
success: true,
duration,
Expand All @@ -154,15 +130,10 @@ export class Bundler {
}
} else {
buildLogger.error("Server bundle failed")

// Restore original files since build failed
await liveComponentsGenerator.postBuild(false)

// Restore plugins registry
const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
await fluxPluginsGenerator.postBuild(false)


// Run post-build cleanup
await this.runPostBuildCleanup(liveComponentsGenerator)

const stderr = await new Response(buildProcess.stderr).text()
return {
success: false,
Expand All @@ -172,21 +143,139 @@ export class Bundler {
}
} catch (error) {
const duration = Date.now() - startTime


// 🧹 CLEANUP: Restore original files on error
try {
await this.runPostBuildCleanup(liveComponentsGenerator)
} catch (cleanupError) {
buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
}

return {
success: false,
duration,
error: error instanceof Error ? error.message : "Unknown error"
}
}
}

async compileToExecutable(entryPoint: string, outputName: string = "app", options: BundleOptions = {}): Promise<BundleResult> {
buildLogger.section('Executable Build', '📦')

const startTime = Date.now()
let liveComponentsGenerator: any = null

try {
// Run pre-build steps
liveComponentsGenerator = await this.runPreBuildSteps()

// Ensure output directory exists
this.ensureOutputDirectory()

const outputPath = join(this.config.outDir, outputName)

// Get external dependencies
const external = this.getExternalDependencies(options)

const buildArgs = [
"bun", "build",
entryPoint,
"--compile",
"--outfile", outputPath,
"--target", this.config.target,
...external.flatMap(ext => ["--external", ext])
]

if (this.config.sourceMaps) {
buildArgs.push("--sourcemap")
}

if (this.config.minify) {
buildArgs.push("--minify")
}

// Add Windows-specific options if provided
if (options.executable?.windows) {
const winOpts = options.executable.windows
if (winOpts.hideConsole) {
buildArgs.push("--windows-hide-console")
}
if (winOpts.icon) {
buildArgs.push("--windows-icon", winOpts.icon)
}
if (winOpts.title) {
buildArgs.push("--windows-title", winOpts.title)
}
if (winOpts.publisher) {
buildArgs.push("--windows-publisher", winOpts.publisher)
}
if (winOpts.version) {
buildArgs.push("--windows-version", winOpts.version)
}
if (winOpts.description) {
buildArgs.push("--windows-description", winOpts.description)
}
if (winOpts.copyright) {
buildArgs.push("--windows-copyright", winOpts.copyright)
}
}

// Add custom build arguments if provided
if (options.executable?.customArgs) {
buildArgs.push(...options.executable.customArgs)
}

buildLogger.step(`Compiling ${entryPoint} to ${outputPath}...`)

const buildProcess = spawn({
cmd: buildArgs,
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
NODE_ENV: 'production',
...options.env
}
})

const exitCode = await buildProcess.exited
const duration = Date.now() - startTime

if (exitCode === 0) {
buildLogger.success(`Executable compiled in ${buildLogger.formatDuration(duration)}`)

// Run post-build cleanup
await this.runPostBuildCleanup(liveComponentsGenerator)

return {
success: true,
duration,
outputPath,
entryPoint: outputPath
}
} else {
buildLogger.error("Executable compilation failed")

// Run post-build cleanup
await this.runPostBuildCleanup(liveComponentsGenerator)

const stderr = await new Response(buildProcess.stderr).text()
return {
success: false,
duration,
error: stderr || "Executable compilation failed"
}
}
} catch (error) {
const duration = Date.now() - startTime

// 🧹 CLEANUP: Restore original files on error
try {
if (liveComponentsGenerator) {
await liveComponentsGenerator.postBuild(false)
}

// Cleanup plugins registry
const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
await fluxPluginsGenerator.postBuild(false)
await this.runPostBuildCleanup(liveComponentsGenerator)
} catch (cleanupError) {
buildLogger.warn(`Failed to cleanup generated files: ${cleanupError}`)
}

return {
success: false,
duration,
Expand All @@ -195,6 +284,61 @@ export class Bundler {
}
}

/**
* Get list of external dependencies that should not be bundled
*/
private getExternalDependencies(options: BundleOptions = {}): string[] {
return [
"@tailwindcss/vite",
"tailwindcss",
"lightningcss",
"vite",
"@vitejs/plugin-react",
"rollup",
...(this.config.external || []),
...(options.external || [])
]
}

/**
* Ensure output directory exists
*/
private ensureOutputDirectory(): void {
if (!existsSync(this.config.outDir)) {
mkdirSync(this.config.outDir, { recursive: true })
}
}

/**
* Run pre-build steps (Live Components and Plugins generation)
*/
private async runPreBuildSteps(): Promise<any> {
// 🚀 PRE-BUILD: Auto-generate Live Components registration
const generatorModule = await import('./live-components-generator')
const liveComponentsGenerator = generatorModule.liveComponentsGenerator
await liveComponentsGenerator.preBuild()

// 🔌 PRE-BUILD: Auto-generate FluxStack Plugins registration
const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
await fluxPluginsGenerator.preBuild()

return liveComponentsGenerator
}

/**
* Run post-build cleanup
*/
private async runPostBuildCleanup(liveComponentsGenerator: any): Promise<void> {
if (liveComponentsGenerator) {
await liveComponentsGenerator.postBuild(false)
}

const pluginsGeneratorModule = await import('./flux-plugins-generator')
const fluxPluginsGenerator = pluginsGeneratorModule.fluxPluginsGenerator
await fluxPluginsGenerator.postBuild(false)
}

private async getClientAssets(): Promise<string[]> {
// This would analyze the build output to get asset information
// For now, return empty array - can be enhanced later
Expand Down
4 changes: 4 additions & 0 deletions core/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export class FluxStackBuilder {
return await this.bundler.bundleServer("app/server/index.ts")
}

async buildExecutable(outputName: string = "CLauncher", options?: import("../types/build").BundleOptions) {
return await this.bundler.compileToExecutable("app/server/index.ts", outputName, options)
}

async createDockerFiles() {
buildLogger.section('Docker Configuration', '🐳')

Expand Down
12 changes: 6 additions & 6 deletions core/cli/generators/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,15 @@ export default {{camelName}}Config
},
{
path: 'plugins/{{name}}/index.ts',
content: `import type { FluxStackPlugin, PluginContext } from '@/core/types/plugin'
content: `import type { ErrorContext, FluxStack, PluginContext, RequestContext, ResponseContext } from "@/core/plugins/types"
// ✅ Plugin imports its own configuration
import { {{camelName}}Config } from './config'

/**
* {{pascalName}} Plugin
* {{description}}
*/
export class {{pascalName}}Plugin implements FluxStackPlugin {
export class {{pascalName}}Plugin implements FluxStack.Plugin {
name = '{{name}}'
version = '1.0.0'

Expand Down Expand Up @@ -173,7 +173,7 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
/**
* Request hook - called on each request
*/
async onRequest?(context: PluginContext, request: Request): Promise<void> {
async onRequest?(context: RequestContext): Promise<void> {
if (!{{camelName}}Config.enabled) return

// Add request processing logic
Expand All @@ -182,7 +182,7 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
/**
* Response hook - called on each response
*/
async onResponse?(context: PluginContext, response: Response): Promise<void> {
async onResponse?(context: ResponseContext): Promise<void> {
if (!{{camelName}}Config.enabled) return

// Add response processing logic
Expand All @@ -191,8 +191,8 @@ export class {{pascalName}}Plugin implements FluxStackPlugin {
/**
* Error hook - called when errors occur
*/
async onError?(context: PluginContext, error: Error): Promise<void> {
console.error(\`[{{name}}] Error:\`, error)
async onError?(context: ErrorContext): Promise<void> {
console.error(\`[{{name}}] Error:\`, context.error)

// Add error handling logic
}
Expand Down
Loading
Loading