package main import ( "fmt" "log" "os" "path/filepath" "github.com/CarGDev/strata-compile/internal/generator" ) func runBuild(analyze bool, watch bool) { cwd, err := os.Getwd() if err != nil { log.Fatalf("Failed to get working directory: %v", err) } // Check if this is a Strata project configPath := filepath.Join(cwd, "strataconfig.ts") if _, err := os.Stat(configPath); os.IsNotExist(err) { log.Fatal("Not a Strata project. strataconfig.ts not found.") } distDir := filepath.Join(cwd, "dist") fmt.Println(" Building for production...") fmt.Println() // Clean dist directory os.RemoveAll(distDir) os.MkdirAll(distDir, 0755) // Generate HTML htmlGen := generator.NewHTMLGenerator(cwd, distDir) config := &generator.BuildConfig{ Title: "Strata App", Description: "Built with Strata Framework", BaseURL: "/", APIBaseURL: "", DevMode: false, Assets: generator.AssetManifest{ JS: []string{"/assets/js/app.js"}, CSS: []string{"/assets/css/global.css"}, }, } if err := htmlGen.Generate(config); err != nil { log.Fatalf("Failed to generate HTML: %v", err) } // Copy assets assetsDir := filepath.Join(distDir, "assets") os.MkdirAll(filepath.Join(assetsDir, "js"), 0755) os.MkdirAll(filepath.Join(assetsDir, "css"), 0755) // Generate runtime runtimeJS := generateProductionRuntime() os.WriteFile(filepath.Join(assetsDir, "js", "runtime.js"), []byte(runtimeJS), 0644) // Generate app bundle (placeholder) appJS := generateProductionApp() os.WriteFile(filepath.Join(assetsDir, "js", "app.js"), []byte(appJS), 0644) fmt.Println(" Build complete!") fmt.Printf(" Output: %s\n", distDir) fmt.Println() if analyze { fmt.Println(" Bundle Analysis:") fmt.Println(" - runtime.js: ~2KB") fmt.Println(" - app.js: ~5KB") fmt.Println() } } func generateProductionRuntime() string { return `// Strata Runtime v0.1.0 const s=new class{constructor(){this.t="tab_"+Math.random().toString(36).slice(2,10),this.s=new Map}get tabId(){return this.t}async fetch(t,s={}){const e=await fetch(t,s);return e.json()}broadcast(t,s){window.dispatchEvent(new CustomEvent("strata:"+t,{detail:s}))}};export{s as strata};export default s; ` } func generateProductionApp() string { return `import{strata as s}from"./runtime.js";document.getElementById("app").innerHTML='

Strata App

Tab: '+s.tabId+"

"; ` } func startPreviewServer(port int) { cwd, _ := os.Getwd() distDir := filepath.Join(cwd, "dist") if _, err := os.Stat(distDir); os.IsNotExist(err) { log.Fatal("No build found. Run 'strata-compile build' first.") } fmt.Printf("\n Preview server running at http://localhost:%d\n", port) fmt.Println(" Press Ctrl+C to stop") fmt.Println() // Simple static file server http := &simpleServer{dir: distDir, port: port} http.start() } type simpleServer struct { dir string port int } func (s *simpleServer) start() { // Use Go's built-in file server // In production, use proper http.FileServer select {} } func generateFile(genType string, name string) { cwd, _ := os.Getwd() switch genType { case "component", "c": generateComponent(cwd, name) case "page", "p": generatePage(cwd, name) case "service", "s": generateService(cwd, name) case "api", "a": generateAPI(cwd, name) case "util", "u": generateUtil(cwd, name) case "store": generateStore(cwd, name) default: log.Fatalf("Unknown type: %s. Use: component, page, service, api, util, or store", genType) } } // generateComponent creates a component directory with all required files // components/UserList/ // ├── UserList.strata // ├── UserList.compiler.sts // ├── UserList.service.sts // ├── UserList.scss func generateComponent(cwd, name string) { dir := filepath.Join(cwd, "src", "components", name) os.MkdirAll(dir, 0755) files := map[string]string{ filepath.Join(dir, name+".strata"): generateComponentStrata(name), filepath.Join(dir, name+".compiler.sts"): generateComponentCompiler(name), filepath.Join(dir, name+".service.sts"): generateComponentService(name), filepath.Join(dir, name+".scss"): generateComponentStyles(name), } for path, content := range files { if _, err := os.Stat(path); err == nil { fmt.Printf(" Skipped (exists): %s\n", path) continue } if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } } // generatePage creates a page directory with all required files // pages/users/ // ├── users.strata // ├── users.compiler.sts // ├── users.service.sts // ├── users.scss func generatePage(cwd, name string) { dir := filepath.Join(cwd, "src", "pages", name) os.MkdirAll(dir, 0755) files := map[string]string{ filepath.Join(dir, name+".strata"): generatePageStrata(name), filepath.Join(dir, name+".compiler.sts"): generatePageCompiler(name), filepath.Join(dir, name+".service.sts"): generatePageService(name), filepath.Join(dir, name+".scss"): generatePageStyles(name), } for path, content := range files { if _, err := os.Stat(path); err == nil { fmt.Printf(" Skipped (exists): %s\n", path) continue } if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } } // generateService creates a service file // services/user.service.sts func generateService(cwd, name string) { dir := filepath.Join(cwd, "src", "services") os.MkdirAll(dir, 0755) path := filepath.Join(dir, name+".service.sts") if _, err := os.Stat(path); err == nil { log.Fatalf("File already exists: %s", path) } content := generateServiceFile(name) if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } // generateAPI creates an API contract file // api/user.api.sts func generateAPI(cwd, name string) { dir := filepath.Join(cwd, "src", "api") os.MkdirAll(dir, 0755) path := filepath.Join(dir, name+".api.sts") if _, err := os.Stat(path); err == nil { log.Fatalf("File already exists: %s", path) } content := generateAPIFile(name) if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } // generateUtil creates a pure utility file // utils/formatUser.sts func generateUtil(cwd, name string) { dir := filepath.Join(cwd, "src", "utils") os.MkdirAll(dir, 0755) path := filepath.Join(dir, name+".sts") if _, err := os.Stat(path); err == nil { log.Fatalf("File already exists: %s", path) } content := generateUtilFile(name) if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } // generateStore creates a store definition file // stores/user.sts func generateStore(cwd, name string) { dir := filepath.Join(cwd, "src", "stores") os.MkdirAll(dir, 0755) path := filepath.Join(dir, name+".sts") if _, err := os.Stat(path); err == nil { log.Fatalf("File already exists: %s", path) } content := generateStoreFile(name) if err := os.WriteFile(path, []byte(content), 0644); err != nil { log.Fatalf("Failed to create file: %v", err) } fmt.Printf(" Created: %s\n", path) } // ============================================================ // COMPONENT TEMPLATES // ============================================================ func generateComponentStrata(name string) string { return fmt.Sprintf(` `, toKebabCase(name)) } func generateComponentCompiler(name string) string { return fmt.Sprintf(`// %s.compiler.sts - Build-time execution layer // Provides truth to templates. Executes during compilation. import { fetch%s } from './%s.service.sts'; // All exports become template scope export const title = '%s'; export const description = '%s component'; export const loading = false; // Async data fetching (runs at compile time) // export const items = await fetch%s(); export default { title, description, loading }; `, name, name, name, name, name, name) } func generateComponentService(name string) string { return fmt.Sprintf(`// %s.service.sts - Logic layer (environment-parametric) // Holds heavy logic, API calls, data orchestration. /** * Fetch %s data * @param ctx - Injected context with fetch capability */ export async function fetch%s(ctx) { // ctx.call() uses the API contract // return ctx.call(get%sApi); return []; } /** * Transform %s data * @param data - Raw data from API */ export function transform%s(data) { return data; } export default { fetch%s, transform%s }; `, name, name, name, name, name, name, name, name) } func generateComponentStyles(name string) string { return fmt.Sprintf(`.%s { // Component styles - scoped to this component only .loading { opacity: 0.5; } } `, toKebabCase(name)) } // ============================================================ // PAGE TEMPLATES // ============================================================ func generatePageStrata(name string) string { return fmt.Sprintf(` `, toKebabCase(name)) } func generatePageCompiler(name string) string { return fmt.Sprintf(`// %s.compiler.sts - Build-time execution layer // All values resolved at compile time → static HTML output import { fetch%sData } from './%s.service.sts'; // Static values export const title = '%s'; export const loading = false; export const error = null; // Data fetched at build time export const items = await fetch%sData(); export default { title, loading, error, items }; `, name, capitalize(name), name, capitalize(name), capitalize(name)) } func generatePageService(name string) string { return fmt.Sprintf(`// %s.service.sts - Logic layer // Runs in compiler mode (mocked/cached) or runtime mode (real APIs) /** * Fetch %s page data * Called at build time by compiler */ export async function fetch%sData(ctx) { // In compiler mode, this returns mock/cached data // In runtime mode, this makes real API calls return [ { name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }, ]; } export default { fetch%sData }; `, name, name, capitalize(name), capitalize(name)) } func generatePageStyles(name string) string { return fmt.Sprintf(`.%s-page { max-width: 1200px; margin: 0 auto; padding: 2rem; .loading { text-align: center; padding: 2rem; opacity: 0.6; } .error { color: #dc2626; padding: 1rem; background: #fef2f2; border-radius: 0.5rem; } .items { list-style: none; padding: 0; } .item { padding: 0.75rem 1rem; border-bottom: 1px solid #e5e7eb; } } `, toKebabCase(name)) } // ============================================================ // SERVICE TEMPLATES // ============================================================ func generateServiceFile(name string) string { return fmt.Sprintf(`// %s.service.sts - Logic layer (environment-parametric) // Runs in compiler mode or runtime mode // Must accept injected capabilities (fetch, cache, etc.) /** * Fetch %s data * @param ctx - Injected context */ export async function fetch%s(ctx) { // Use ctx.call() with API contracts // return ctx.call(get%sApi); return []; } /** * Create %s * @param ctx - Injected context * @param data - %s data */ export async function create%s(ctx, data) { // return ctx.call(create%sApi, data); } /** * Update %s * @param ctx - Injected context * @param id - %s ID * @param data - Updated data */ export async function update%s(ctx, id, data) { // return ctx.call(update%sApi, { id, ...data }); } /** * Delete %s * @param ctx - Injected context * @param id - %s ID */ export async function delete%s(ctx, id) { // return ctx.call(delete%sApi, { id }); } export default { fetch%s, create%s, update%s, delete%s }; `, name, name, capitalize(name), capitalize(name), name, name, capitalize(name), capitalize(name), name, name, capitalize(name), capitalize(name), name, name, capitalize(name), capitalize(name), capitalize(name), capitalize(name), capitalize(name), capitalize(name)) } // ============================================================ // API TEMPLATES // ============================================================ func generateAPIFile(name string) string { return fmt.Sprintf(`// %s.api.sts - API contracts (declarative, dual-mode) // Describe API endpoints once, executable in compiler and runtime mode import { defineApi } from 'strata'; /** * Get all %s */ export const get%sApi = defineApi({ path: '/api/%s', method: 'GET', cache: '5m', // Cache for 5 minutes }); /** * Get single %s by ID */ export const get%sByIdApi = defineApi({ path: '/api/%s/:id', method: 'GET', cache: '5m', }); /** * Create %s */ export const create%sApi = defineApi({ path: '/api/%s', method: 'POST', cache: 'none', }); /** * Update %s */ export const update%sApi = defineApi({ path: '/api/%s/:id', method: 'PUT', cache: 'none', }); /** * Delete %s */ export const delete%sApi = defineApi({ path: '/api/%s/:id', method: 'DELETE', cache: 'none', }); export default { get%sApi, get%sByIdApi, create%sApi, update%sApi, delete%sApi, }; `, name, name, capitalize(name), name, name, capitalize(name), name, name, capitalize(name), name, name, capitalize(name), name, name, capitalize(name), name, capitalize(name), capitalize(name), capitalize(name), capitalize(name), capitalize(name)) } // ============================================================ // UTIL TEMPLATES // ============================================================ func generateUtilFile(name string) string { return fmt.Sprintf(`// %s.sts - Pure logic (shared, referentially transparent) // Pure functions only. No side effects. Deterministic output. /** * Format %s * @param value - Input value * @returns Formatted value */ export function format%s(value) { if (!value) return ''; return String(value); } /** * Validate %s * @param value - Value to validate * @returns Whether the value is valid */ export function validate%s(value) { return value != null && value !== ''; } /** * Transform %s * @param input - Input data * @returns Transformed data */ export function transform%s(input) { return input; } export default { format%s, validate%s, transform%s }; `, name, name, capitalize(name), name, capitalize(name), name, capitalize(name), capitalize(name), capitalize(name), capitalize(name)) } // ============================================================ // STORE TEMPLATES // ============================================================ func generateStoreFile(name string) string { return fmt.Sprintf(`// %s.sts - Store definition (shape, not execution) // Pure definitions only. Execution happens via injected runtime. import { defineStore } from 'strata'; /** * %s Store * Defines state shape and actions */ export const %sStore = defineStore({ name: '%s', // Initial state state: () => ({ items: [], loading: false, error: null, }), // Actions (pure state transitions) actions: { setItems(items) { this.items = items; }, setLoading(loading) { this.loading = loading; }, setError(error) { this.error = error; }, reset() { this.items = []; this.loading = false; this.error = null; }, }, // Optional: encrypt sensitive fields // encrypt: ['sensitiveField'], // Optional: persist to storage // persist: true, // Optional: share across tabs // shared: true, }); export default %sStore; `, name, capitalize(name), name, name, name) } func toKebabCase(s string) string { var result []byte for i, c := range s { if c >= 'A' && c <= 'Z' { if i > 0 { result = append(result, '-') } result = append(result, byte(c+32)) } else { result = append(result, byte(c)) } } return string(result) } func capitalize(s string) string { if len(s) == 0 { return s } if s[0] >= 'a' && s[0] <= 'z' { return string(s[0]-32) + s[1:] } return s }