This commit is contained in:
Josh 2026-01-09 00:24:04 +02:00
commit 91a950e72f
17 changed files with 2724 additions and 0 deletions

228
main.go Normal file
View file

@ -0,0 +1,228 @@
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/nicepkg/jarvis/transform"
)
type CompileRequest struct {
Language string `json:"language"`
Source string `json:"source"`
}
type CompileResponse struct {
Success bool `json:"success"`
Wasm []byte `json:"wasm,omitempty"`
Size int `json:"size,omitempty"`
TimeMS int64 `json:"time_ms,omitempty"`
Errors []string `json:"errors,omitempty"`
}
type Language struct {
Value string `json:"value"`
Label string `json:"label"`
}
var languages = []Language{
{Value: "typescript", Label: "TypeScript"},
{Value: "go", Label: "Go"},
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8090"
}
http.HandleFunc("/health", healthHandler)
http.HandleFunc("/compile", compileHandler)
http.HandleFunc("/languages", languagesHandler)
http.HandleFunc("/hooks", hooksHandler)
http.HandleFunc("/template", templateHandler)
http.HandleFunc("/sdk", sdkHandler)
http.HandleFunc("/lsp", lspHandler)
log.Printf("Jarvis listening on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
func languagesHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(languages)
}
func hooksHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(hooks)
}
func templateHandler(w http.ResponseWriter, r *http.Request) {
hook := r.URL.Query().Get("hook")
language := r.URL.Query().Get("language")
if hook == "" {
hook = "post.published"
}
if language == "" {
language = "typescript"
}
template := GetTemplate(hook, language)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"template": template,
"hook": hook,
"language": language,
})
}
func compileHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req CompileRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, "Invalid request body")
return
}
start := time.Now()
var resp CompileResponse
switch req.Language {
case "typescript":
resp = compileTypeScript(req.Source)
case "go":
resp = compileGo(req.Source)
default:
respondError(w, "Unsupported language: "+req.Language)
return
}
resp.TimeMS = time.Since(start).Milliseconds()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func respondError(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(CompileResponse{Success: false, Errors: []string{msg}})
}
func compileTypeScript(source string) CompileResponse {
// Transform source from clean API to Extism-compatible format
transformed, err := transform.TypeScript(source)
if err != nil {
return CompileResponse{Success: false, Errors: []string{"Transform error: " + err.Error()}}
}
tmpDir, err := os.MkdirTemp("", "jarvis-ts-*")
if err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
defer os.RemoveAll(tmpDir)
srcFile := filepath.Join(tmpDir, "plugin.ts")
outFile := filepath.Join(tmpDir, "plugin.wasm")
dtsFile := filepath.Join(tmpDir, "index.d.ts")
if err := os.WriteFile(srcFile, []byte(transformed), 0644); err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
// Copy the Extism d.ts file - extism-js requires interface declarations
sdkDts, err := os.ReadFile("/app/sdk/extism.d.ts")
if err != nil {
return CompileResponse{Success: false, Errors: []string{"Failed to read SDK d.ts: " + err.Error()}}
}
if err := os.WriteFile(dtsFile, sdkDts, 0644); err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
cmd := exec.Command("extism-js", srcFile, "-i", dtsFile, "-o", outFile)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return CompileResponse{Success: false, Errors: []string{stderr.String()}}
}
wasm, err := os.ReadFile(outFile)
if err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
return CompileResponse{Success: true, Wasm: wasm, Size: len(wasm)}
}
func compileGo(source string) CompileResponse {
// Transform source from clean API to Extism-compatible format
transformed, err := transform.Go(source)
if err != nil {
return CompileResponse{Success: false, Errors: []string{"Transform error: " + err.Error()}}
}
tmpDir, err := os.MkdirTemp("", "jarvis-go-*")
if err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
defer os.RemoveAll(tmpDir)
srcFile := filepath.Join(tmpDir, "main.go")
outFile := filepath.Join(tmpDir, "plugin.wasm")
if err := os.WriteFile(srcFile, []byte(transformed), 0644); err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte(`
module writekit-plugin
go 1.22
require github.com/extism/go-pdk v1.0.6
`), 0644); err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
// Run go mod tidy to fetch dependencies and create go.sum
tidyCmd := exec.Command("go", "mod", "tidy")
tidyCmd.Dir = tmpDir
var tidyStderr bytes.Buffer
tidyCmd.Stderr = &tidyStderr
if err := tidyCmd.Run(); err != nil {
return CompileResponse{Success: false, Errors: []string{"Failed to tidy dependencies: " + tidyStderr.String()}}
}
cmd := exec.Command("tinygo", "build", "-o", outFile, "-target", "wasi", srcFile)
cmd.Dir = tmpDir
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return CompileResponse{Success: false, Errors: []string{stderr.String()}}
}
wasm, err := os.ReadFile(outFile)
if err != nil {
return CompileResponse{Success: false, Errors: []string{err.Error()}}
}
return CompileResponse{Success: true, Wasm: wasm, Size: len(wasm)}
}