init
This commit is contained in:
commit
91a950e72f
17 changed files with 2724 additions and 0 deletions
228
main.go
Normal file
228
main.go
Normal 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)}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue