initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
poiesis
|
||||
43
AGENTS.md
Normal file
43
AGENTS.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Poiesis
|
||||
|
||||
## Overview
|
||||
|
||||
Go tool that transpiles TypeScript to JavaScript using esbuild API and executes it with goja.
|
||||
|
||||
## Build & Test
|
||||
|
||||
```bash
|
||||
go build
|
||||
go test
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `main.go` - Entry point with `executeTypeScript()` function
|
||||
- `main_test.go` - Test suite using testify assertions
|
||||
- `test_data/` - Test TypeScript files
|
||||
- `go.mod` - Dependencies
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
- **Test framework**: Go's built-in `testing` package
|
||||
- **Assertions**: `github.com/stretchr/testify/assert`
|
||||
- **Linting**: `golangci-lint run` - must pass before committing
|
||||
- **Test organization**: Test files use `_test.go` suffix, test functions prefixed with `Test`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation
|
||||
- `github.com/dop251/goja` - JavaScript execution
|
||||
- `github.com/stretchr/testify/assert` - Test assertions
|
||||
|
||||
## Key Functions
|
||||
|
||||
- `executeTypeScript(filePath string, stdout, stderr io.Writer) error` - Main transpilation and execution logic
|
||||
|
||||
## Code Conventions
|
||||
|
||||
- Handle all return values from external functions (enforced by golangci-lint)
|
||||
- Use `os` package instead of deprecated `io/ioutil`
|
||||
- Error logging uses `_, _ = fmt.Fprintf(stderr, ...)` pattern
|
||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769318308,
|
||||
"narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
35
flake.nix
Normal file
35
flake.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
description = "Development Environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self
|
||||
, nixpkgs
|
||||
, flake-utils
|
||||
,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = (
|
||||
import nixpkgs {
|
||||
system = system;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
# Backend
|
||||
go
|
||||
gopls
|
||||
golangci-lint
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
20
go.mod
Normal file
20
go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module poiesis
|
||||
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3
|
||||
github.com/evanw/esbuild v0.27.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // 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/text v0.3.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
28
go.sum
Normal file
28
go.sum
Normal file
@@ -0,0 +1,28 @@
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3 h1:bVp3yUzvSAJzu9GqID+Z96P+eu5TKnIMJSV4QaZMauM=
|
||||
github.com/dop251/goja v0.0.0-20260106131823-651366fbe6e3/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/evanw/esbuild v0.27.2 h1:3xBEws9y/JosfewXMM2qIyHAi+xRo8hVx475hVkJfNg=
|
||||
github.com/evanw/esbuild v0.27.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
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/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
72
main.go
Normal file
72
main.go
Normal file
@@ -0,0 +1,72 @@
|
||||
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()
|
||||
|
||||
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
Normal file
69
main_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
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")
|
||||
}
|
||||
24
test_data/test.ts
Normal file
24
test_data/test.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
interface Person {
|
||||
name: string;
|
||||
age: number;
|
||||
email: string;
|
||||
}
|
||||
|
||||
function greet(person: Person): string {
|
||||
return `Hello, ${person.name}! You are ${person.age} years old.`;
|
||||
}
|
||||
|
||||
const user: Person = {
|
||||
name: "Alice",
|
||||
age: 30,
|
||||
email: "alice@example.com",
|
||||
};
|
||||
|
||||
console.log(greet(user));
|
||||
console.log(`Email: ${user.email}`);
|
||||
|
||||
function calculateSum(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
console.log(`Sum of 5 and 10 is: ${calculateSum(5, 10)}`);
|
||||
Reference in New Issue
Block a user