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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ runtime-debug.js
runtime.js
.task/*
/package.json
.worktrees/
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ Essential software development tools for everyday tasks.

## Features

### **Browser Support**

DevToolbox now works in both desktop and browser modes:

- **Desktop**: Native Wails application with native performance (default)
- **Browser**: Access via `http://localhost:8081` when the desktop app is running

The frontend automatically detects the environment and uses the appropriate API (Wails runtime for desktop, HTTP for browser). See [docs/BROWSER_MODE.md](docs/BROWSER_MODE.md) for details.

### **Text Based Converter** (Unified Tool)
The central hub with 45+ algorithms across 5 categories:

Expand Down
173 changes: 173 additions & 0 deletions cmd/genservices/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package main

import (
_ "embed"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
)

//go:embed templates/typescript.tmpl
var typescriptTemplate string

// Generator generates TypeScript code
type Generator struct {
outputDir string
tmpl *template.Template
}

// NewGenerator creates a new generator
func NewGenerator(outputDir string) (*Generator, error) {
tmpl, err := template.New("typescript").Parse(typescriptTemplate)
if err != nil {
return nil, err
}

return &Generator{
outputDir: outputDir,
tmpl: tmpl,
}, nil
}

// Generate creates TypeScript files for all services
func (g *Generator) Generate(services []Service) error {
// Create output directories
wailsDir := filepath.Join(g.outputDir, "wails")
httpDir := filepath.Join(g.outputDir, "http")

os.MkdirAll(wailsDir, 0755)
os.MkdirAll(httpDir, 0755)

// Generate individual service files
for _, service := range services {
if err := g.generateWailsService(wailsDir, service); err != nil {
return err
}
if err := g.generateHTTPService(httpDir, service); err != nil {
return err
}
}

// Generate index files
if err := g.generateWailsIndex(wailsDir, services); err != nil {
return err
}
if err := g.generateHTTPIndex(httpDir, services); err != nil {
return err
}

// Generate unified facade
return g.generateUnifiedFacade(g.outputDir, services)
}

func (g *Generator) generateWailsService(dir string, service Service) error {
filename := filepath.Join(dir, toCamelCase(service.Name)+".ts")

data := struct {
ServiceName string
Methods []ServiceMethod
}{
ServiceName: service.Name,
Methods: service.Methods,
}

file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()

return g.tmpl.ExecuteTemplate(file, "wails", data)
}

func (g *Generator) generateHTTPService(dir string, service Service) error {
filename := filepath.Join(dir, toCamelCase(service.Name)+".ts")

data := struct {
ServiceName string
Methods []ServiceMethod
}{
ServiceName: service.Name,
Methods: service.Methods,
}

file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()

return g.tmpl.ExecuteTemplate(file, "http", data)
}

func (g *Generator) generateWailsIndex(dir string, services []Service) error {
filename := filepath.Join(dir, "index.ts")

var exports []string
for _, svc := range services {
exports = append(exports, fmt.Sprintf("export * as %s from './%s';",
toCamelCase(svc.Name), toCamelCase(svc.Name)))
}

content := strings.Join(exports, "\n")
return os.WriteFile(filename, []byte(content), 0644)
}

func (g *Generator) generateHTTPIndex(dir string, services []Service) error {
filename := filepath.Join(dir, "index.ts")

var exports []string
for _, svc := range services {
exports = append(exports, fmt.Sprintf("export * as %s from './%s';",
toCamelCase(svc.Name), toCamelCase(svc.Name)))
}

content := strings.Join(exports, "\n")
return os.WriteFile(filename, []byte(content), 0644)
}

func (g *Generator) generateUnifiedFacade(dir string, services []Service) error {
filename := filepath.Join(dir, "index.ts")

var serviceImports []string
var serviceMappings []string

for _, svc := range services {
camelName := toCamelCase(svc.Name)
serviceImports = append(serviceImports, fmt.Sprintf(
"import { %s as Wails%s } from './wails/%s';\n"+
"import { %s as HTTP%s } from './http/%s';",
svc.Name, svc.Name, camelName,
svc.Name, svc.Name, camelName))

serviceMappings = append(serviceMappings, fmt.Sprintf(
"export const %s = isWails() ? Wails%s : HTTP%s;",
camelName, svc.Name, svc.Name))
}

content := fmt.Sprintf(`// Auto-generated unified service facade
// Detects runtime environment and uses appropriate implementation

const isWails = () => {
return typeof window !== 'undefined' &&
window.runtime &&
window.runtime.EventsOn !== undefined;
};

%s

%s
`, strings.Join(serviceImports, "\n"), strings.Join(serviceMappings, "\n"))

return os.WriteFile(filename, []byte(content), 0644)
}

// toCamelCase converts PascalCase to camelCase
func toCamelCase(s string) string {
if s == "" {
return s
}
return strings.ToLower(s[:1]) + s[1:]
}
54 changes: 54 additions & 0 deletions cmd/genservices/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"flag"
"fmt"
"log"
"path/filepath"
)

func main() {
var (
serviceDir = flag.String("services", "service", "Directory containing Go service files")
outputDir = flag.String("output", "frontend/src/generated", "Output directory for generated TypeScript")
)
flag.Parse()

// Get absolute paths
absServiceDir, err := filepath.Abs(*serviceDir)
if err != nil {
log.Fatal(err)
}

absOutputDir, err := filepath.Abs(*outputDir)
if err != nil {
log.Fatal(err)
}

fmt.Printf("Parsing services from: %s\n", absServiceDir)
fmt.Printf("Generating TypeScript to: %s\n", absOutputDir)

// Parse services
parser := NewParser(absServiceDir)
services, err := parser.ParseServices()
if err != nil {
log.Fatal("Failed to parse services:", err)
}

fmt.Printf("Found %d services\n", len(services))
for _, svc := range services {
fmt.Printf(" - %s (%d methods)\n", svc.Name, len(svc.Methods))
}

// Generate TypeScript
generator, err := NewGenerator(absOutputDir)
if err != nil {
log.Fatal("Failed to create generator:", err)
}

if err := generator.Generate(services); err != nil {
log.Fatal("Failed to generate TypeScript:", err)
}

fmt.Println("✓ Generation complete!")
}
Loading
Loading