git commit -m "feat: initial release of Strata framework v0.1.0
- Static compiler with STRC pattern (Static Template Resolution with
Compartmentalized Layers)
- Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
}
- File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
.scss
- CLI tools: strata dev, strata build, strata g (generators)
- create-strata scaffolding CLI with Pokemon API example
- Dev server with WebSocket HMR (Hot Module Replacement)
- Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
LICENSE
This commit is contained in:
695
compiler/cmd/strata/build.go
Normal file
695
compiler/cmd/strata/build.go
Normal file
@@ -0,0 +1,695 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/CarGDev/strata/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='<main style="max-width:800px;margin:0 auto;padding:2rem"><h1>Strata App</h1><p>Tab: '+s.tabId+"</p></main>";
|
||||
`
|
||||
}
|
||||
|
||||
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 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(`<template>
|
||||
<div class="%s">
|
||||
{ s-if loading }
|
||||
<p class="loading">Loading...</p>
|
||||
{ s-else }
|
||||
<h2>{ title }</h2>
|
||||
<p>{ description }</p>
|
||||
{ /s-if }
|
||||
</div>
|
||||
</template>
|
||||
`, 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(`<template>
|
||||
<main class="%s-page">
|
||||
<h1>{ title }</h1>
|
||||
|
||||
{ s-if loading }
|
||||
<div class="loading">Loading...</div>
|
||||
{ s-elif error }
|
||||
<div class="error">{ error }</div>
|
||||
{ s-else }
|
||||
<ul class="items">
|
||||
{ s-for item in items }
|
||||
<li class="item">{ item.name }</li>
|
||||
{ /s-for }
|
||||
</ul>
|
||||
{ /s-if }
|
||||
</main>
|
||||
</template>
|
||||
`, 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
|
||||
}
|
||||
96
compiler/cmd/strata/dev.go
Normal file
96
compiler/cmd/strata/dev.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/CarGDev/strata/internal/server"
|
||||
"github.com/CarGDev/strata/internal/watcher"
|
||||
)
|
||||
|
||||
func startDevServer(port int, open bool) {
|
||||
// Get current working directory
|
||||
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.")
|
||||
}
|
||||
|
||||
// Create dev server
|
||||
devServer := server.NewDevServer(port, cwd)
|
||||
|
||||
// Create file watcher
|
||||
srcDir := filepath.Join(cwd, "src")
|
||||
fileWatcher, err := watcher.New(srcDir, func(event watcher.ChangeEvent) {
|
||||
fmt.Printf(" Changed: %s\n", event.Path)
|
||||
|
||||
// Rebuild the project
|
||||
if err := devServer.Rebuild(); err != nil {
|
||||
fmt.Printf(" Rebuild error: %v\n", err)
|
||||
}
|
||||
|
||||
// Determine change type for HMR
|
||||
changeType := "reload"
|
||||
switch event.Type {
|
||||
case watcher.FileTypeSCSS:
|
||||
changeType = "css"
|
||||
case watcher.FileTypeStrata:
|
||||
changeType = "component"
|
||||
case watcher.FileTypeConfig:
|
||||
changeType = "reload"
|
||||
}
|
||||
|
||||
devServer.NotifyChange(changeType, event.Path)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create watcher: %v", err)
|
||||
}
|
||||
|
||||
// Start watcher in background
|
||||
go func() {
|
||||
if err := fileWatcher.Start(); err != nil {
|
||||
log.Printf("Watcher error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Open browser if requested
|
||||
if open {
|
||||
openBrowser(fmt.Sprintf("http://localhost:%d", port))
|
||||
}
|
||||
|
||||
// Start server (blocks)
|
||||
if err := devServer.Start(); err != nil {
|
||||
log.Fatalf("Server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func openBrowser(url string) {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
cmd = exec.Command("open", url)
|
||||
case "linux":
|
||||
cmd = exec.Command("xdg-open", url)
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", "start", url)
|
||||
default:
|
||||
fmt.Printf(" Open manually: %s\n", url)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Printf(" Could not open browser: %v\n", err)
|
||||
fmt.Printf(" Open manually: %s\n", url)
|
||||
}
|
||||
}
|
||||
99
compiler/cmd/strata/main.go
Normal file
99
compiler/cmd/strata/main.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var version = "0.1.0"
|
||||
|
||||
func main() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "strata",
|
||||
Short: "Strata - Static Template Rendering Architecture",
|
||||
Version: version,
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(devCmd())
|
||||
rootCmd.AddCommand(buildCmd())
|
||||
rootCmd.AddCommand(previewCmd())
|
||||
rootCmd.AddCommand(generateCmd())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func devCmd() *cobra.Command {
|
||||
var port int
|
||||
var noOpen bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "dev",
|
||||
Short: "Start development server with file watcher",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Starting Strata dev server on port %d...\n", port)
|
||||
startDevServer(port, !noOpen)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntVarP(&port, "port", "p", 3000, "Port to run dev server")
|
||||
cmd.Flags().BoolVar(&noOpen, "no-open", false, "Don't open browser on start")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func buildCmd() *cobra.Command {
|
||||
var analyze bool
|
||||
var watch bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build",
|
||||
Short: "Build for production",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Building Strata application...")
|
||||
runBuild(analyze, watch)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&analyze, "analyze", "a", false, "Analyze bundle size")
|
||||
cmd.Flags().BoolVarP(&watch, "watch", "w", false, "Watch mode")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func previewCmd() *cobra.Command {
|
||||
var port int
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "preview",
|
||||
Short: "Preview production build",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Previewing build on port %d...\n", port)
|
||||
startPreviewServer(port)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntVarP(&port, "port", "p", 4000, "Port for preview server")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func generateCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "generate [type] [name]",
|
||||
Aliases: []string{"g"},
|
||||
Short: "Generate component, page, or store",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
genType := args[0]
|
||||
name := args[1]
|
||||
generateFile(genType, name)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
Reference in New Issue
Block a user