Skip to content
Draft
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
30 changes: 24 additions & 6 deletions cmd/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,15 @@ func ToStringSlice(args []any) []string {
}

// GetWorkflowLanguage determines the workflow language based on the file extension
// Note: inputFile can be a file path (e.g., "main.ts" or "main.go") or a directory (for Go workflows, e.g., ".")
// Returns constants.WorkflowLanguageTypeScript for .ts or .tsx files, constants.WorkflowLanguageGolang otherwise
// Note: inputFile can be a file path (e.g., "main.ts", "main.go", or "workflow.wasm") or a directory (for Go workflows, e.g., ".")
// Returns constants.WorkflowLanguageTypeScript for .ts or .tsx files, constants.WorkflowLanguageWasm for .wasm files, constants.WorkflowLanguageGolang otherwise
func GetWorkflowLanguage(inputFile string) string {
if strings.HasSuffix(inputFile, ".ts") || strings.HasSuffix(inputFile, ".tsx") {
return constants.WorkflowLanguageTypeScript
}
if strings.HasSuffix(inputFile, ".wasm") {
return constants.WorkflowLanguageWasm
}
return constants.WorkflowLanguageGolang
}

Expand All @@ -183,19 +186,23 @@ func EnsureTool(bin string) error {
return nil
}

// Gets a build command for either Golang or Typescript based on the filename
// Gets a build command for Golang, TypeScript, or WASM based on the workflow language
func GetBuildCmd(inputFile string, outputFile string, rootFolder string) *exec.Cmd {
isTypescriptWorkflow := strings.HasSuffix(inputFile, ".ts") || strings.HasSuffix(inputFile, ".tsx")
language := GetWorkflowLanguage(inputFile)

var buildCmd *exec.Cmd
if isTypescriptWorkflow {
switch language {
case constants.WorkflowLanguageTypeScript:
buildCmd = exec.Command(
"bun",
"cre-compile",
inputFile,
outputFile,
)
} else {
case constants.WorkflowLanguageWasm:
// For WASM workflows, use make build
buildCmd = exec.Command("make", "build")
case constants.WorkflowLanguageGolang:
// The build command for reproducible and trimmed binaries.
// -trimpath removes all file system paths from the compiled binary.
// -ldflags="-buildid= -w -s" further reduces the binary size:
Expand All @@ -211,6 +218,17 @@ func GetBuildCmd(inputFile string, outputFile string, rootFolder string) *exec.C
inputFile,
)
buildCmd.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm", "CGO_ENABLED=0")
default:
// Fallback to Go for unknown languages
buildCmd = exec.Command(
"go",
"build",
"-o", outputFile,
"-trimpath",
"-ldflags=-buildid= -w -s",
inputFile,
)
buildCmd.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm", "CGO_ENABLED=0")
}

buildCmd.Dir = rootFolder
Expand Down
36 changes: 32 additions & 4 deletions cmd/creinit/creinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ const SecretsFileName = "secrets.yaml"
type TemplateLanguage string

const (
TemplateLangGo TemplateLanguage = "go"
TemplateLangTS TemplateLanguage = "typescript"
TemplateLangGo TemplateLanguage = "go"
TemplateLangTS TemplateLanguage = "typescript"
TemplateLangWasm TemplateLanguage = "wasm"
)

const (
Expand Down Expand Up @@ -75,6 +76,14 @@ var languageTemplates = []LanguageTemplate{
{Folder: "typescriptConfHTTP", Title: "Confidential Http: Typescript example using the confidential http capability", ID: 5, Name: ConfHTTPTemplate, Hidden: true},
},
},
{
Title: "Self-compiled WASM (advanced)",
Lang: TemplateLangWasm,
EntryPoint: "./wasm/workflow.wasm",
Workflows: []WorkflowTemplate{
{Folder: "wasmBlankTemplate", Title: "Blank: Self-compiled WASM workflow template", ID: 6, Name: HelloWorldTemplate},
},
},
}

type Inputs struct {
Expand Down Expand Up @@ -365,14 +374,17 @@ func (h *handler) Execute(inputs Inputs) error {
h.runtimeContext.Workflow.Language = constants.WorkflowLanguageGolang
case TemplateLangTS:
h.runtimeContext.Workflow.Language = constants.WorkflowLanguageTypeScript
case TemplateLangWasm:
h.runtimeContext.Workflow.Language = constants.WorkflowLanguageWasm
}
}

fmt.Println("\nWorkflow initialized successfully!")
fmt.Println("")
fmt.Println("Next steps:")

if selectedLanguageTemplate.Lang == TemplateLangGo {
switch selectedLanguageTemplate.Lang {
case TemplateLangGo:
fmt.Println(" 1. Navigate to your project directory:")
fmt.Printf(" cd %s\n", filepath.Base(projectRoot))
fmt.Println("")
Expand All @@ -382,7 +394,7 @@ func (h *handler) Execute(inputs Inputs) error {
fmt.Printf(" 3. (Optional) Consult %s to learn more about this template:\n\n",
filepath.Join(filepath.Base(workflowDirectory), "README.md"))
fmt.Println("")
} else {
case TemplateLangTS:
fmt.Println(" 1. Navigate to your project directory:")
fmt.Printf(" cd %s\n", filepath.Base(projectRoot))
fmt.Println("")
Expand All @@ -398,6 +410,22 @@ func (h *handler) Execute(inputs Inputs) error {
fmt.Printf(" 5. (Optional) Consult %s to learn more about this template:\n\n",
filepath.Join(filepath.Base(workflowDirectory), "README.md"))
fmt.Println("")
case TemplateLangWasm:
fmt.Println(" 1. Navigate to your project directory:")
fmt.Printf(" cd %s\n", filepath.Base(projectRoot))
fmt.Println("")
fmt.Println(" 2. Add your build logic to the Makefile:")
fmt.Printf(" Edit %s/Makefile and implement the 'build' target\n", filepath.Base(workflowDirectory))
fmt.Println("")
fmt.Println(" 3. Build your workflow:")
fmt.Printf(" cd %s && make build\n", filepath.Base(workflowDirectory))
fmt.Println("")
fmt.Println(" 4. Run the workflow on your machine:")
fmt.Printf(" cre workflow simulate %s\n", workflowName)
fmt.Println("")
fmt.Printf(" 5. (Optional) Consult %s to learn more about this template:\n\n",
filepath.Join(filepath.Base(workflowDirectory), "README.md"))
fmt.Println("")
}
return nil
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/creinit/template/workflow/wasmBlankTemplate/Makefile.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.PHONY: build

build:
# TODO: Add your build logic here
# This target should compile your workflow to wasm/workflow.wasm
# Example for Go:
# GOOS=wasip1 GOARCH=wasm go build -o wasm/workflow.wasm .
# Example for Rust:
# cargo build --target wasm32-wasi --release
# cp target/wasm32-wasi/release/workflow.wasm wasm/workflow.wasm
@echo "Please implement the build target in the Makefile"
@exit 1
35 changes: 35 additions & 0 deletions cmd/creinit/template/workflow/wasmBlankTemplate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Self-compiled WASM Workflow Template

This template provides a blank workflow template for self-compiled WASM workflows. It includes the necessary files for a workflow, excluding workflow code.

## Structure

- `Makefile`: Contains a TODO on the `build` target where you should add your build logic
- `workflow.yaml`: Workflow settings file with the wasm directory configured
- `config.staging.json` and `config.production.json`: Configuration files for different environments
- `secrets.yaml`: Secrets file (if needed)

## Steps to use

1. **Add your build logic**: Edit the `Makefile` and implement the `build` target. This should compile your workflow to `wasm/workflow.wasm`.

2. **Build your workflow**: Run `make build` from the workflow directory.

3. **Simulate the workflow**: From the project root, run:
```bash
cre workflow simulate <workflow-name> --target=staging-settings
```

## Example Makefile build target

```makefile
build:
# TODO: Add your build logic here
# Example for Go:
# GOOS=wasip1 GOARCH=wasm go build -o wasm/workflow.wasm .
# Example for Rust:
# cargo build --target wasm32-wasi --release
# cp target/wasm32-wasi/release/workflow.wasm wasm/workflow.wasm
@echo "Please implement the build target in the Makefile"
exit 1
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
secretsNames:
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# WASM Directory

This directory should contain your compiled WASM file (`workflow.wasm`) after running `make build`.
87 changes: 66 additions & 21 deletions cmd/workflow/deploy/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ func (h *handler) Compile() error {
}

workflowRootFolder := filepath.Dir(h.inputs.WorkflowPath)

tmpWasmFileName := "tmp.wasm"
workflowMainFile := filepath.Base(h.inputs.WorkflowPath)

// Set language in runtime context based on workflow file extension
Expand All @@ -61,31 +59,73 @@ func (h *handler) Compile() error {
if err := cmdcommon.EnsureTool("go"); err != nil {
return errors.New("go toolchain is required for Go workflows but was not found in PATH; install from https://go.dev/dl")
}
case constants.WorkflowLanguageWasm:
if err := cmdcommon.EnsureTool("make"); err != nil {
return errors.New("make is required for WASM workflows but was not found in PATH")
}
default:
return fmt.Errorf("unsupported workflow language for file %s", workflowMainFile)
}
}

buildCmd := cmdcommon.GetBuildCmd(workflowMainFile, tmpWasmFileName, workflowRootFolder)
h.log.Debug().
Str("Workflow directory", buildCmd.Dir).
Str("Command", buildCmd.String()).
Msg("Executing go build command")
var wasmFile []byte

buildOutput, err := buildCmd.CombinedOutput()
if err != nil {
fmt.Println(string(buildOutput))
// For WASM workflows, if the path already points to a .wasm file, use it directly
if h.runtimeContext != nil && h.runtimeContext.Workflow.Language == constants.WorkflowLanguageWasm {
if strings.HasSuffix(h.inputs.WorkflowPath, ".wasm") {
// Use the WASM file directly
wasmFile, err = os.ReadFile(h.inputs.WorkflowPath)
if err != nil {
return fmt.Errorf("failed to read WASM file: %w", err)
}
} else {
// Build the workflow using make build
tmpWasmFileName := "tmp.wasm"
buildCmd := cmdcommon.GetBuildCmd(workflowMainFile, tmpWasmFileName, workflowRootFolder)
h.log.Debug().
Str("Workflow directory", buildCmd.Dir).
Str("Command", buildCmd.String()).
Msg("Executing make build command")

buildOutput, err := buildCmd.CombinedOutput()
if err != nil {
fmt.Println(string(buildOutput))
out := strings.TrimSpace(string(buildOutput))
return fmt.Errorf("failed to build workflow: %w\nbuild output:\n%s", err, out)
}
h.log.Debug().Msgf("Build output: %s", buildOutput)
fmt.Println("Workflow compiled successfully")

out := strings.TrimSpace(string(buildOutput))
return fmt.Errorf("failed to compile workflow: %w\nbuild output:\n%s", err, out)
}
h.log.Debug().Msgf("Build output: %s", buildOutput)
fmt.Println("Workflow compiled successfully")
tmpWasmLocation := filepath.Join(workflowRootFolder, tmpWasmFileName)
wasmFile, err = os.ReadFile(tmpWasmLocation)
if err != nil {
return fmt.Errorf("failed to read workflow binary: %w", err)
}
}
} else {
// For Go and TypeScript workflows, compile as before
tmpWasmFileName := "tmp.wasm"
buildCmd := cmdcommon.GetBuildCmd(workflowMainFile, tmpWasmFileName, workflowRootFolder)
h.log.Debug().
Str("Workflow directory", buildCmd.Dir).
Str("Command", buildCmd.String()).
Msg("Executing build command")

buildOutput, err := buildCmd.CombinedOutput()
if err != nil {
fmt.Println(string(buildOutput))

out := strings.TrimSpace(string(buildOutput))
return fmt.Errorf("failed to compile workflow: %w\nbuild output:\n%s", err, out)
}
h.log.Debug().Msgf("Build output: %s", buildOutput)
fmt.Println("Workflow compiled successfully")

tmpWasmLocation := filepath.Join(workflowRootFolder, tmpWasmFileName)
wasmFile, err := os.ReadFile(tmpWasmLocation)
if err != nil {
return fmt.Errorf("failed to read workflow binary: %w", err)
tmpWasmLocation := filepath.Join(workflowRootFolder, tmpWasmFileName)
wasmFile, err = os.ReadFile(tmpWasmLocation)
if err != nil {
return fmt.Errorf("failed to read workflow binary: %w", err)
}
}

compressedFile, err := applyBrotliCompressionV2(&wasmFile)
Expand All @@ -99,8 +139,13 @@ func (h *handler) Compile() error {
}
h.log.Debug().Msg("WASM binary encoded")

if err = os.Remove(tmpWasmLocation); err != nil {
return fmt.Errorf("failed to remove the temporary file: %w", err)
// Only remove tmp file if we created one (not for direct WASM file usage)
// Check if we used a tmp file (i.e., not a direct .wasm file and not WASM language that used direct file)
if !strings.HasSuffix(h.inputs.WorkflowPath, ".wasm") {
tmpWasmLocation := filepath.Join(workflowRootFolder, "tmp.wasm")
if err = os.Remove(tmpWasmLocation); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove the temporary file: %w", err)
}
}

return nil
Expand Down
Loading
Loading