wip2
This commit is contained in:
39
AGENTS.md
39
AGENTS.md
@@ -1,28 +1,46 @@
|
|||||||
# Poiesis
|
# Poiesis
|
||||||
|
|
||||||
|
## Module Name
|
||||||
|
|
||||||
|
`reichard.io/poiesis`
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Go tool that transpiles TypeScript to JavaScript using esbuild API and executes it with goja.
|
Go tool that transpiles TypeScript to JavaScript using esbuild API and executes it with goja. Features a flexible builtin system for exposing Go functions to TypeScript.
|
||||||
|
|
||||||
## Build & Test
|
## Build & Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build
|
go build ./cmd/poiesis
|
||||||
go test
|
go test ./...
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
- `main.go` - Entry point with `executeTypeScript()` function
|
```
|
||||||
- `main_test.go` - Test suite using testify assertions
|
reichard.io/poiesis/
|
||||||
- `test_data/` - Test TypeScript files
|
├── cmd/poiesis/ # CLI entry point
|
||||||
- `go.mod` - Dependencies
|
│ └── main.go
|
||||||
|
├── internal/
|
||||||
|
│ ├── builtin/ # Builtin function framework
|
||||||
|
│ │ ├── builtin.go
|
||||||
|
│ │ └── builtin_test.go
|
||||||
|
│ └── runtime/ # TypeScript transpilation + execution
|
||||||
|
│ ├── runtime.go
|
||||||
|
│ └── runtime_test.go
|
||||||
|
└── test_data/ # Test TypeScript files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Packages
|
||||||
|
|
||||||
|
- `reichard.io/poiesis/internal/runtime` - Runtime management, TypeScript transpilation, execution
|
||||||
|
- `reichard.io/poiesis/internal/builtin` - Builtin registration and type conversion
|
||||||
|
|
||||||
## Testing Patterns
|
## Testing Patterns
|
||||||
|
|
||||||
- **Test framework**: Go's built-in `testing` package
|
- **Test framework**: Go's built-in `testing` package
|
||||||
- **Assertions**: `github.com/stretchr/testify/assert`
|
- **Assertions**: `github.com/stretchr/testify/assert` and `require`
|
||||||
- **Linting**: `golangci-lint run` - must pass before committing
|
- **Linting**: `golangci-lint run` - must pass before committing
|
||||||
- **Test organization**: Test files use `_test.go` suffix, test functions prefixed with `Test`
|
- **Test organization**: Test files use `_test.go` suffix, test functions prefixed with `Test`
|
||||||
|
|
||||||
@@ -32,12 +50,9 @@ golangci-lint run
|
|||||||
- `github.com/dop251/goja` - JavaScript execution
|
- `github.com/dop251/goja` - JavaScript execution
|
||||||
- `github.com/stretchr/testify/assert` - Test assertions
|
- `github.com/stretchr/testify/assert` - Test assertions
|
||||||
|
|
||||||
## Key Functions
|
|
||||||
|
|
||||||
- `executeTypeScript(filePath string, stdout, stderr io.Writer) error` - Main transpilation and execution logic
|
|
||||||
|
|
||||||
## Code Conventions
|
## Code Conventions
|
||||||
|
|
||||||
- Handle all return values from external functions (enforced by golangci-lint)
|
- Handle all return values from external functions (enforced by golangci-lint)
|
||||||
- Use `os` package instead of deprecated `io/ioutil`
|
- Use `os` package instead of deprecated `io/ioutil`
|
||||||
- Error logging uses `_, _ = fmt.Fprintf(stderr, ...)` pattern
|
- Error logging uses `_, _ = fmt.Fprintf(stderr, ...)` pattern
|
||||||
|
- Package structure follows standard Go project layout with internal packages
|
||||||
|
|||||||
89
README.md
Normal file
89
README.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# Poiesis
|
||||||
|
|
||||||
|
A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with goja, with an extensible builtin system.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
reichard.io/poiesis/
|
||||||
|
├── cmd/
|
||||||
|
│ └── poiesis/ # CLI application entry point
|
||||||
|
│ └── main.go
|
||||||
|
├── internal/
|
||||||
|
│ ├── builtin/ # Builtin function framework
|
||||||
|
│ │ ├── builtin.go
|
||||||
|
│ │ └── builtin_test.go
|
||||||
|
│ └── runtime/ # TypeScript transpilation and execution
|
||||||
|
│ ├── runtime.go
|
||||||
|
│ └── runtime_test.go
|
||||||
|
└── examples/ # Example TypeScript files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation & Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build ./cmd/poiesis
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test ./...
|
||||||
|
golangci-lint run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poiesis <typescript-file>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builtin System
|
||||||
|
|
||||||
|
The builtin system allows you to easily expose Go functions to TypeScript/JavaScript.
|
||||||
|
|
||||||
|
### Adding a Builtin
|
||||||
|
|
||||||
|
Just write a Go function and register it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Your function
|
||||||
|
func add(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register it
|
||||||
|
func init() {
|
||||||
|
builtin.RegisterBuiltin("add", add)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! The framework automatically:
|
||||||
|
- Converts TypeScript values to Go types
|
||||||
|
- Handles errors (panics as JS errors)
|
||||||
|
- Generates TypeScript definitions
|
||||||
|
- Manages the goja integration
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// TypeScript code
|
||||||
|
console.log("5 + 10 =", add(5, 10));
|
||||||
|
|
||||||
|
const response = fetch("https://httpbin.org/get");
|
||||||
|
console.log("OK:", response.ok);
|
||||||
|
console.log("Status:", response.status);
|
||||||
|
console.log("Body:", response.text());
|
||||||
|
```
|
||||||
|
|
||||||
|
### Built-in Functions
|
||||||
|
|
||||||
|
- `fetch(url, options?)` - HTTP requests
|
||||||
|
- `add(a, b)` - Simple arithmetic example
|
||||||
|
- `greet(name)` - String manipulation example
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation
|
||||||
|
- `github.com/dop251/goja` - JavaScript execution
|
||||||
|
- `github.com/stretchr/testify/assert` - Test assertions
|
||||||
4
go.mod
4
go.mod
@@ -1,10 +1,11 @@
|
|||||||
module poiesis
|
module reichard.io/poiesis
|
||||||
|
|
||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
|
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
|
||||||
github.com/evanw/esbuild v0.27.2
|
github.com/evanw/esbuild v0.27.2
|
||||||
|
github.com/stretchr/testify v1.11.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -13,7 +14,6 @@ require (
|
|||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||||
golang.org/x/text v0.3.8 // indirect
|
golang.org/x/text v0.3.8 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
1
go.sum
1
go.sum
@@ -21,6 +21,7 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9w
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package main
|
package builtin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -53,36 +52,3 @@ func TestFetchWithInvalidURL(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "failed to fetch")
|
assert.Contains(t, err.Error(), "failed to fetch")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchBuiltinIntegration(t *testing.T) {
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
w.Header().Set("X-Custom", "custom-value")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write([]byte("Hello, World!"))
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
var stdout, stderr strings.Builder
|
|
||||||
tsContent := `
|
|
||||||
const response = fetch("${URL}");
|
|
||||||
console.log("OK:", response.ok);
|
|
||||||
console.log("Status:", response.status);
|
|
||||||
console.log("Body:", response.text());
|
|
||||||
|
|
||||||
console.log("Content-Type:", response.headers.get("content-type") || "undefined");
|
|
||||||
console.log("Content-Type (case sensitive):", response.headers.get("Content-Type") || "undefined");
|
|
||||||
console.log("X-Custom:", response.headers.get("x-custom") || "undefined");
|
|
||||||
console.log("X-Custom (case sensitive):", response.headers.get("X-Custom") || "undefined");
|
|
||||||
`
|
|
||||||
tsContent = strings.Replace(tsContent, "${URL}", server.URL, 1)
|
|
||||||
|
|
||||||
err := executeTypeScriptContent(tsContent, &stdout, &stderr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Empty(t, stderr.String(), "Expected no error output")
|
|
||||||
|
|
||||||
output := stdout.String()
|
|
||||||
assert.Contains(t, output, "OK: true")
|
|
||||||
assert.Contains(t, output, "Status: 200")
|
|
||||||
assert.Contains(t, output, "Body: Hello, World!")
|
|
||||||
}
|
|
||||||
137
internal/runtime/runtime.go
Normal file
137
internal/runtime/runtime.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/evanw/esbuild/pkg/api"
|
||||||
|
"reichard.io/poiesis/internal/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Runtime struct {
|
||||||
|
vm *goja.Runtime
|
||||||
|
stdout io.Writer
|
||||||
|
stderr io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Runtime {
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
r := &Runtime{vm: vm, stdout: os.Stdout, stderr: os.Stderr}
|
||||||
|
r.setupConsole()
|
||||||
|
builtin.RegisterBuiltins(vm)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) setupConsole() {
|
||||||
|
console := r.vm.NewObject()
|
||||||
|
_ = r.vm.Set("console", console)
|
||||||
|
|
||||||
|
_ = console.Set("log", func(call goja.FunctionCall) goja.Value {
|
||||||
|
args := call.Arguments
|
||||||
|
for i, arg := range args {
|
||||||
|
if i > 0 {
|
||||||
|
_, _ = fmt.Fprint(r.stdout, " ")
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprint(r.stdout, arg.String())
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(r.stdout)
|
||||||
|
return goja.Undefined()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) SetOutput(stdout, stderr io.Writer) {
|
||||||
|
r.stdout = stdout
|
||||||
|
r.stderr = stderr
|
||||||
|
consoleObj := r.vm.Get("console")
|
||||||
|
if consoleObj != nil {
|
||||||
|
console := consoleObj.ToObject(r.vm)
|
||||||
|
if console != nil {
|
||||||
|
r.setupConsole()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) RunFile(filePath string, stdout, stderr io.Writer) error {
|
||||||
|
r.stdout = stdout
|
||||||
|
r.stderr = stderr
|
||||||
|
r.setupConsole()
|
||||||
|
|
||||||
|
content, err := r.transformFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "Error: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(content.errors) > 0 {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
||||||
|
for _, err := range content.errors {
|
||||||
|
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("transpilation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.vm.RunString(content.code)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) RunCode(tsCode string, stdout, stderr io.Writer) error {
|
||||||
|
r.stdout = stdout
|
||||||
|
r.stderr = stderr
|
||||||
|
r.setupConsole()
|
||||||
|
|
||||||
|
content := r.transformCode(tsCode)
|
||||||
|
|
||||||
|
if len(content.errors) > 0 {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
||||||
|
for _, err := range content.errors {
|
||||||
|
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("transpilation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := r.vm.RunString(content.code)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformResult struct {
|
||||||
|
code string
|
||||||
|
errors []api.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) transformFile(filePath string) (*transformResult, error) {
|
||||||
|
tsFileContent, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.transformCode(string(tsFileContent)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) transformCode(tsCode string) *transformResult {
|
||||||
|
result := api.Transform(tsCode, api.TransformOptions{
|
||||||
|
Loader: api.LoaderTS,
|
||||||
|
Target: api.ES2020,
|
||||||
|
Format: api.FormatIIFE,
|
||||||
|
Sourcemap: api.SourceMapNone,
|
||||||
|
TreeShaking: api.TreeShakingFalse,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &transformResult{
|
||||||
|
code: string(result.Code),
|
||||||
|
errors: result.Errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
internal/runtime/runtime_test.go
Normal file
44
internal/runtime/runtime_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExecuteTypeScript(t *testing.T) {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
rt := New()
|
||||||
|
err := rt.RunFile("../../test_data/test.ts", &stdout, &stderr)
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Expected no error")
|
||||||
|
assert.Empty(t, stderr.String(), "Expected no error output")
|
||||||
|
|
||||||
|
output := stdout.String()
|
||||||
|
|
||||||
|
assert.Contains(t, output, "Hello, Alice!", "Should greet Alice")
|
||||||
|
assert.Contains(t, output, "You are 30 years old", "Should show age")
|
||||||
|
assert.Contains(t, output, "Email: alice@example.com", "Should show email")
|
||||||
|
assert.Contains(t, output, "Sum of 5 and 10 is: 15", "Should calculate sum correctly")
|
||||||
|
|
||||||
|
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||||
|
assert.GreaterOrEqual(t, len(lines), 3, "Should have at least 3 output lines")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchBuiltinIntegration(t *testing.T) {
|
||||||
|
rt := New()
|
||||||
|
|
||||||
|
tsContent := `
|
||||||
|
const result = add(5, 10);
|
||||||
|
console.log("Result:", result);
|
||||||
|
`
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
err := rt.RunCode(tsContent, &stdout, &stderr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, stdout.String(), "Result: 15")
|
||||||
|
}
|
||||||
119
main.go
119
main.go
@@ -1,119 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
|
||||||
"github.com/evanw/esbuild/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func executeTypeScript(filePath string, stdout, stderr io.Writer) error {
|
|
||||||
tsContent, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Error reading file: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := api.Transform(string(tsContent), api.TransformOptions{
|
|
||||||
Loader: api.LoaderTS,
|
|
||||||
Target: api.ES2020,
|
|
||||||
Format: api.FormatIIFE,
|
|
||||||
Sourcemap: api.SourceMapNone,
|
|
||||||
TreeShaking: api.TreeShakingFalse,
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(result.Errors) > 0 {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
|
||||||
for _, err := range result.Errors {
|
|
||||||
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transpilation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := goja.New()
|
|
||||||
|
|
||||||
RegisterBuiltins(vm)
|
|
||||||
|
|
||||||
console := vm.NewObject()
|
|
||||||
_ = vm.Set("console", console)
|
|
||||||
|
|
||||||
_ = console.Set("log", func(call goja.FunctionCall) goja.Value {
|
|
||||||
args := call.Arguments
|
|
||||||
for i, arg := range args {
|
|
||||||
if i > 0 {
|
|
||||||
_, _ = fmt.Fprint(stdout, " ")
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprint(stdout, arg.String())
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(stdout)
|
|
||||||
return goja.Undefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err = vm.RunString(string(result.Code))
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func executeTypeScriptContent(tsContent string, stdout, stderr io.Writer) error {
|
|
||||||
result := api.Transform(tsContent, api.TransformOptions{
|
|
||||||
Loader: api.LoaderTS,
|
|
||||||
Target: api.ES2020,
|
|
||||||
Format: api.FormatIIFE,
|
|
||||||
Sourcemap: api.SourceMapNone,
|
|
||||||
TreeShaking: api.TreeShakingFalse,
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(result.Errors) > 0 {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Transpilation errors:\n")
|
|
||||||
for _, err := range result.Errors {
|
|
||||||
_, _ = fmt.Fprintf(stderr, " %s\n", err.Text)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transpilation failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := goja.New()
|
|
||||||
|
|
||||||
RegisterBuiltins(vm)
|
|
||||||
|
|
||||||
console := vm.NewObject()
|
|
||||||
_ = vm.Set("console", console)
|
|
||||||
|
|
||||||
_ = console.Set("log", func(call goja.FunctionCall) goja.Value {
|
|
||||||
args := call.Arguments
|
|
||||||
for i, arg := range args {
|
|
||||||
if i > 0 {
|
|
||||||
_, _ = fmt.Fprint(stdout, " ")
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprint(stdout, arg.String())
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintln(stdout)
|
|
||||||
return goja.Undefined()
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err := vm.RunString(string(result.Code))
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(stderr, "Execution error: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
fmt.Fprintln(os.Stderr, "Usage: program <typescript-file>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := os.Args[1]
|
|
||||||
|
|
||||||
if err := executeTypeScript(filePath, os.Stdout, os.Stderr); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
69
main_test.go
69
main_test.go
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExecuteTypeScript(t *testing.T) {
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
|
|
||||||
err := executeTypeScript("test_data/test.ts", &stdout, &stderr)
|
|
||||||
|
|
||||||
assert.NoError(t, err, "Expected no error")
|
|
||||||
assert.Empty(t, stderr.String(), "Expected no error output")
|
|
||||||
|
|
||||||
output := stdout.String()
|
|
||||||
|
|
||||||
assert.Contains(t, output, "Hello, Alice!", "Should greet Alice")
|
|
||||||
assert.Contains(t, output, "You are 30 years old", "Should show age")
|
|
||||||
assert.Contains(t, output, "Email: alice@example.com", "Should show email")
|
|
||||||
assert.Contains(t, output, "Sum of 5 and 10 is: 15", "Should calculate sum correctly")
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
|
||||||
assert.GreaterOrEqual(t, len(lines), 3, "Should have at least 3 output lines")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecuteTypeScriptWithNonExistentFile(t *testing.T) {
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
|
|
||||||
err := executeTypeScript("non_existent_file.ts", &stdout, &stderr)
|
|
||||||
|
|
||||||
assert.Error(t, err, "Expected error for non-existent file")
|
|
||||||
assert.Contains(t, stderr.String(), "Error reading file", "Should show read error")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecuteTypeScriptWithSyntaxError(t *testing.T) {
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
|
|
||||||
tsContent := `
|
|
||||||
interface Person {
|
|
||||||
name: string;
|
|
||||||
age: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user: Person = {
|
|
||||||
name: "Bob",
|
|
||||||
age: 25,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(user.name)
|
|
||||||
console.log(user.age +);
|
|
||||||
`
|
|
||||||
|
|
||||||
err := os.WriteFile("test_data/invalid.ts", []byte(tsContent), 0644)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to write test file: %v", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = os.Remove("test_data/invalid.ts")
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = executeTypeScript("test_data/invalid.ts", &stdout, &stderr)
|
|
||||||
|
|
||||||
assert.Error(t, err, "Expected error for invalid TypeScript")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user