Skip to content
Open
38 changes: 28 additions & 10 deletions cmd/common/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,31 @@ const makefileName = "Makefile"

var defaultWasmOutput = filepath.Join("wasm", "workflow.wasm")

const (
// SkipTypeChecksFlag is passed through to cre-compile for TypeScript workflows (matches @chainlink/cre-sdk).
SkipTypeChecksFlag = "--skip-type-checks"
// SkipTypeChecksCLIFlag is the Cobra/Viper flag name (no leading dashes).
SkipTypeChecksCLIFlag = "skip-type-checks"
)

// WorkflowCompileOptions configures workflow compilation for CompileWorkflowToWasm.
type WorkflowCompileOptions struct {
// StripSymbols, when true, strips debug symbols from Go WASM builds (smaller binary for deploy).
StripSymbols bool
// SkipTypeChecks, when true, passes SkipTypeChecksFlag to cre-compile for TypeScript workflows.
SkipTypeChecks bool
}

// getBuildCmd returns a single step that builds the workflow and returns the WASM bytes.
// If stripSymbols is true, debug symbols are stripped from the binary to reduce size.
func getBuildCmd(workflowRootFolder, mainFile, language string, stripSymbols bool) (func() ([]byte, error), error) {
func getBuildCmd(workflowRootFolder, mainFile, language string, opts WorkflowCompileOptions) (func() ([]byte, error), error) {
tmpPath := filepath.Join(workflowRootFolder, ".cre_build_tmp.wasm")
switch language {
case constants.WorkflowLanguageTypeScript:
cmd := exec.Command("bun", "cre-compile", mainFile, tmpPath)
args := []string{"cre-compile", mainFile, tmpPath}
if opts.SkipTypeChecks {
args = append(args, SkipTypeChecksFlag)
}
cmd := exec.Command("bun", args...)
cmd.Dir = workflowRootFolder
return func() ([]byte, error) {
out, err := cmd.CombinedOutput()
Expand All @@ -37,7 +55,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, stripSymbols boo
case constants.WorkflowLanguageGolang:
// Build the package (.) so all .go files (main.go, workflow.go, etc.) are compiled together
ldflags := "-buildid="
if stripSymbols {
if opts.StripSymbols {
ldflags = "-buildid= -w -s"
}
cmd := exec.Command(
Expand Down Expand Up @@ -78,7 +96,7 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, stripSymbols boo
default:
// Build the package (.) so all .go files are compiled together
ldflags := "-buildid="
if stripSymbols {
if opts.StripSymbols {
ldflags = "-buildid= -w -s"
}
cmd := exec.Command(
Expand All @@ -105,10 +123,10 @@ func getBuildCmd(workflowRootFolder, mainFile, language string, stripSymbols boo
}

// CompileWorkflowToWasm compiles the workflow at workflowPath and returns the WASM binary.
// If stripSymbols is true, debug symbols are stripped to reduce binary size (used for deploy).
// If false, debug symbols are kept for better error messages (used for simulate).
// For custom builds (WASM language with Makefile), stripSymbols has no effect.
func CompileWorkflowToWasm(workflowPath string, stripSymbols bool) ([]byte, error) {
// opts.StripSymbols: for Go builds, true strips debug symbols (deploy); false keeps them (simulate).
// opts.SkipTypeChecks: for TypeScript, passes SkipTypeChecksFlag to cre-compile.
// For custom Makefile WASM builds, StripSymbols and SkipTypeChecks have no effect.
func CompileWorkflowToWasm(workflowPath string, opts WorkflowCompileOptions) ([]byte, error) {
workflowRootFolder, workflowMainFile, err := WorkflowPathRootAndMain(workflowPath)
if err != nil {
return nil, fmt.Errorf("workflow path: %w", err)
Expand Down Expand Up @@ -140,7 +158,7 @@ func CompileWorkflowToWasm(workflowPath string, stripSymbols bool) ([]byte, erro
return nil, fmt.Errorf("unsupported workflow language for file %s", workflowMainFile)
}

buildStep, err := getBuildCmd(workflowRootFolder, workflowMainFile, language, stripSymbols)
buildStep, err := getBuildCmd(workflowRootFolder, workflowMainFile, language, opts)
if err != nil {
return nil, err
}
Expand Down
29 changes: 20 additions & 9 deletions cmd/common/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ func TestFindMakefileRoot(t *testing.T) {
func TestCompileWorkflowToWasm_Go_Success(t *testing.T) {
t.Run("basic_workflow", func(t *testing.T) {
path := deployTestdataPath("basic_workflow", "main.go")
wasm, err := CompileWorkflowToWasm(path, true)
wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true})
require.NoError(t, err)
assert.NotEmpty(t, wasm)
})

t.Run("configless_workflow", func(t *testing.T) {
path := deployTestdataPath("configless_workflow", "main.go")
wasm, err := CompileWorkflowToWasm(path, true)
wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true})
require.NoError(t, err)
assert.NotEmpty(t, wasm)
})

t.Run("missing_go_mod", func(t *testing.T) {
path := deployTestdataPath("missing_go_mod", "main.go")
wasm, err := CompileWorkflowToWasm(path, true)
wasm, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true})
require.NoError(t, err)
assert.NotEmpty(t, wasm)
})
}

func TestCompileWorkflowToWasm_Go_Malformed_Fails(t *testing.T) {
path := deployTestdataPath("malformed_workflow", "main.go")
_, err := CompileWorkflowToWasm(path, true)
_, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true})
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to compile workflow")
assert.Contains(t, err.Error(), "undefined: sdk.RemovedFunctionThatFailsCompilation")
Expand All @@ -80,7 +80,7 @@ func TestCompileWorkflowToWasm_Wasm_Success(t *testing.T) {
_ = os.Remove(wasmPath)
t.Cleanup(func() { _ = os.Remove(wasmPath) })

wasm, err := CompileWorkflowToWasm(wasmPath, true)
wasm, err := CompileWorkflowToWasm(wasmPath, WorkflowCompileOptions{StripSymbols: true})
require.NoError(t, err)
assert.NotEmpty(t, wasm)

Expand All @@ -96,14 +96,14 @@ func TestCompileWorkflowToWasm_Wasm_Fails(t *testing.T) {
wasmPath := filepath.Join(wasmDir, "workflow.wasm")
require.NoError(t, os.WriteFile(wasmPath, []byte("not really wasm"), 0600))

_, err := CompileWorkflowToWasm(wasmPath, true)
_, err := CompileWorkflowToWasm(wasmPath, WorkflowCompileOptions{StripSymbols: true})
require.Error(t, err)
assert.Contains(t, err.Error(), "no Makefile found")
})

t.Run("make_build_fails", func(t *testing.T) {
path := deployTestdataPath("wasm_make_fails", "wasm", "workflow.wasm")
_, err := CompileWorkflowToWasm(path, true)
_, err := CompileWorkflowToWasm(path, WorkflowCompileOptions{StripSymbols: true})
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to compile workflow")
assert.Contains(t, err.Error(), "build output:")
Expand All @@ -118,7 +118,7 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) {
mainPath := filepath.Join(dir, "main.ts")
require.NoError(t, os.WriteFile(mainPath, []byte(`export async function main() { return "ok"; }
`), 0600))
require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"latest"}}
require.NoError(t, os.WriteFile(filepath.Join(dir, "package.json"), []byte(`{"name":"test","dependencies":{"@chainlink/cre-sdk":"^1.5.0"}}
`), 0600))
install := exec.Command("bun", "install")
install.Dir = dir
Expand All @@ -127,7 +127,18 @@ func TestCompileWorkflowToWasm_TS_Success(t *testing.T) {
if err := install.Run(); err != nil {
t.Skipf("bun install failed (network or cre-sdk): %v", err)
}
wasm, err := CompileWorkflowToWasm(mainPath, true)
require.NoError(t, os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(`{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"types": []
},
"include": ["main.ts"]
}
`), 0600))
wasm, err := CompileWorkflowToWasm(mainPath, WorkflowCompileOptions{StripSymbols: true})
if err != nil {
t.Skipf("TS compile failed (published cre-sdk may lack full layout): %v", err)
}
Expand Down
11 changes: 8 additions & 3 deletions cmd/workflow/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
Example: `cre workflow build ./my-workflow`,
RunE: func(cmd *cobra.Command, args []string) error {
outputPath, _ := cmd.Flags().GetString("output")
return execute(args[0], outputPath)
skipTypeChecks, _ := cmd.Flags().GetBool(cmdcommon.SkipTypeChecksCLIFlag)
return execute(args[0], outputPath, skipTypeChecks)
},
}
buildCmd.Flags().StringP("output", "o", "", "Output file path for the compiled WASM binary (default: <workflow-folder>/binary.wasm)")
buildCmd.Flags().Bool(cmdcommon.SkipTypeChecksCLIFlag, false, "Skip TypeScript project typecheck during compilation (passes "+cmdcommon.SkipTypeChecksFlag+" to cre-compile)")
return buildCmd
}

func execute(workflowFolder, outputPath string) error {
func execute(workflowFolder, outputPath string, skipTypeChecks bool) error {
workflowDir, err := filepath.Abs(workflowFolder)
if err != nil {
return fmt.Errorf("resolve workflow folder: %w", err)
Expand All @@ -58,7 +60,10 @@ func execute(workflowFolder, outputPath string) error {
outputPath = cmdcommon.EnsureWasmExtension(outputPath)

ui.Dim("Compiling workflow...")
wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedPath, true)
wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedPath, cmdcommon.WorkflowCompileOptions{
StripSymbols: true,
SkipTypeChecks: skipTypeChecks,
})
if err != nil {
ui.Error("Build failed:")
return fmt.Errorf("failed to compile workflow: %w", err)
Expand Down
8 changes: 8 additions & 0 deletions cmd/workflow/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ func TestBuildCommandDefaultFlag(t *testing.T) {
assert.Equal(t, "o", f.Shorthand)
}

func TestBuildCommandSkipTypeChecksFlag(t *testing.T) {
t.Parallel()
cmd := New(nil)
f := cmd.Flags().Lookup(cmdcommon.SkipTypeChecksCLIFlag)
require.NotNil(t, f)
assert.Equal(t, "false", f.DefValue)
}

func TestBuildMissingWorkflowYAML(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
Expand Down
5 changes: 3 additions & 2 deletions cmd/workflow/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,10 @@ func makefileContent(workflowDir, lang string, mainFile string) (string, error)
}

func makefileContentTS(_, mainFile string) (string, error) {
return fmt.Sprintf(`.PHONY: build
return fmt.Sprintf(`# Append %s after wasm/workflow.wasm to skip TypeScript typecheck (not recommended for production).
.PHONY: build

build:
bun cre-compile %s wasm/workflow.wasm
`, mainFile), nil
`, cmdcommon.SkipTypeChecksFlag, mainFile), nil
}
2 changes: 1 addition & 1 deletion cmd/workflow/convert/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ production-settings:
`
require.NoError(t, os.WriteFile(workflowYAML, []byte(yamlContent), 0600))
require.NoError(t, os.WriteFile(mainTS, []byte("export default function run() { return Promise.resolve({ result: \"ok\" }); }\n"), 0600))
require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.0.3"}}`), 0600))
require.NoError(t, os.WriteFile(packageJSON, []byte(`{"name":"test","private":true,"dependencies":{"@chainlink/cre-sdk":"^1.5.0"}}`), 0600))

h := newHandler(nil)
err := h.Execute(Inputs{WorkflowFolder: dir, Force: true})
Expand Down
5 changes: 4 additions & 1 deletion cmd/workflow/deploy/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ func (h *handler) Compile() error {
h.runtimeContext.Workflow.Language = cmdcommon.GetWorkflowLanguage(workflowMainFile)
}

wasmFile, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, true)
wasmFile, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{
StripSymbols: true,
SkipTypeChecks: h.inputs.SkipTypeChecks,
})
if err != nil {
ui.Error("Build failed:")
return fmt.Errorf("failed to compile workflow: %w", err)
Expand Down
5 changes: 4 additions & 1 deletion cmd/workflow/deploy/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,10 @@ func outputPathWithExtensions(path string) string {
// file content equals CompileWorkflowToWasm(workflowPath) + brotli + base64.
func assertCompileOutputMatchesUnderlying(t *testing.T, simulatedEnvironment *chainsim.SimulatedEnvironment, inputs Inputs, ownerType string) {
t.Helper()
wasm, err := cmdcommon.CompileWorkflowToWasm(inputs.WorkflowPath, true)
wasm, err := cmdcommon.CompileWorkflowToWasm(inputs.WorkflowPath, cmdcommon.WorkflowCompileOptions{
StripSymbols: true,
SkipTypeChecks: inputs.SkipTypeChecks,
})
require.NoError(t, err)
compressed, err := cmdcommon.CompressBrotli(wasm)
require.NoError(t, err)
Expand Down
4 changes: 4 additions & 0 deletions cmd/workflow/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type Inputs struct {

OwnerLabel string `validate:"omitempty"`
SkipConfirmation bool
// SkipTypeChecks passes --skip-type-checks to cre-compile for TypeScript workflows.
SkipTypeChecks bool
}

func (i *Inputs) ResolveConfigURL(fallbackURL string) string {
Expand Down Expand Up @@ -114,6 +116,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
deployCmd.Flags().String("config", "", "Override the config file path from workflow.yaml")
deployCmd.Flags().Bool("no-config", false, "Deploy without a config file")
deployCmd.Flags().Bool("default-config", false, "Use the config path from workflow.yaml settings (default behavior)")
deployCmd.Flags().Bool(cmdcommon.SkipTypeChecksCLIFlag, false, "Skip TypeScript project typecheck during compilation (passes "+cmdcommon.SkipTypeChecksFlag+" to cre-compile)")
deployCmd.MarkFlagsMutuallyExclusive("config", "no-config", "default-config")

return deployCmd
Expand Down Expand Up @@ -183,6 +186,7 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) {
WorkflowRegistryContractAddress: h.environmentSet.WorkflowRegistryAddress,
OwnerLabel: v.GetString("owner-label"),
SkipConfirmation: v.GetBool(settings.Flags.SkipConfirmation.Name),
SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag),
}, nil
}

Expand Down
12 changes: 9 additions & 3 deletions cmd/workflow/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Inputs struct {
WorkflowPath string
OwnerFromSettings string
PrivateKey string
SkipTypeChecks bool
}

func New(runtimeContext *runtime.Context) *cobra.Command {
Expand All @@ -49,6 +50,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
WorkflowPath: s.Workflow.WorkflowArtifactSettings.WorkflowPath,
OwnerFromSettings: s.Workflow.UserWorkflowSettings.WorkflowOwnerAddress,
PrivateKey: settings.NormalizeHexKey(rawPrivKey),
SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag),
}

return Execute(inputs)
Expand All @@ -64,12 +66,13 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
hashCmd.Flags().Bool("no-config", false, "Hash without a config file")
hashCmd.Flags().Bool("default-config", false, "Use the config path from workflow.yaml settings (default behavior)")
hashCmd.MarkFlagsMutuallyExclusive("config", "no-config", "default-config")
hashCmd.Flags().Bool(cmdcommon.SkipTypeChecksCLIFlag, false, "Skip TypeScript project typecheck during compilation (passes "+cmdcommon.SkipTypeChecksFlag+" to cre-compile)")

return hashCmd
}

func Execute(inputs Inputs) error {
rawBinary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath)
rawBinary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath, inputs.SkipTypeChecks)
if err != nil {
return err
}
Expand Down Expand Up @@ -124,7 +127,7 @@ func ResolveOwner(forUser, ownerFromSettings, privateKey string) (string, error)
return "", fmt.Errorf("cannot determine workflow owner: provide --public_key or ensure CRE_ETH_PRIVATE_KEY is set")
}

func loadBinary(wasmFlag, workflowPathFromSettings string) ([]byte, error) {
func loadBinary(wasmFlag, workflowPathFromSettings string, skipTypeChecks bool) ([]byte, error) {
if wasmFlag != "" {
if cmdcommon.IsURL(wasmFlag) {
ui.Dim("Fetching WASM binary from URL...")
Expand Down Expand Up @@ -155,7 +158,10 @@ func loadBinary(wasmFlag, workflowPathFromSettings string) ([]byte, error) {

spinner := ui.NewSpinner()
spinner.Start("Compiling workflow...")
wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, true)
wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{
StripSymbols: true,
SkipTypeChecks: skipTypeChecks,
})
spinner.Stop()
if err != nil {
ui.Error("Build failed:")
Expand Down
9 changes: 8 additions & 1 deletion cmd/workflow/simulate/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type Inputs struct {
ExperimentalForwarders map[uint64]common.Address `validate:"-"` // forwarders keyed by chain ID
// Limits enforcement
LimitsPath string `validate:"-"` // "default" or path to custom limits JSON
// SkipTypeChecks passes --skip-type-checks to cre-compile for TypeScript workflows.
SkipTypeChecks bool `validate:"-"`
}

func New(runtimeContext *runtime.Context) *cobra.Command {
Expand Down Expand Up @@ -103,6 +105,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command {
simulateCmd.Flags().String("evm-tx-hash", "", "EVM trigger transaction hash (0x...)")
simulateCmd.Flags().Int("evm-event-index", -1, "EVM trigger log index (0-based)")
simulateCmd.Flags().String("limits", "default", "Production limits to enforce during simulation: 'default' for prod defaults, path to a limits JSON file (e.g. from 'cre workflow limits export'), or 'none' to disable")
simulateCmd.Flags().Bool(cmdcommon.SkipTypeChecksCLIFlag, false, "Skip TypeScript project typecheck during compilation (passes "+cmdcommon.SkipTypeChecksFlag+" to cre-compile)")
return simulateCmd
}

Expand Down Expand Up @@ -240,6 +243,7 @@ func (h *handler) ResolveInputs(v *viper.Viper, creSettings *settings.Settings)
EVMEventIndex: v.GetInt("evm-event-index"),
ExperimentalForwarders: experimentalForwarders,
LimitsPath: v.GetString("limits"),
SkipTypeChecks: v.GetBool(cmdcommon.SkipTypeChecksCLIFlag),
}, nil
}

Expand Down Expand Up @@ -330,7 +334,10 @@ func (h *handler) Execute(inputs Inputs) error {

spinner := ui.NewSpinner()
spinner.Start("Compiling workflow...")
wasmFileBinary, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, false)
wasmFileBinary, err = cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath, cmdcommon.WorkflowCompileOptions{
StripSymbols: false,
SkipTypeChecks: inputs.SkipTypeChecks,
})
spinner.Stop()
if err != nil {
ui.Error("Build failed:")
Expand Down
5 changes: 3 additions & 2 deletions docs/cre_workflow_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ cre workflow build ./my-workflow
### Options

```
-h, --help help for build
-o, --output string Output file path for the compiled WASM binary (default: <workflow-folder>/binary.wasm)
-h, --help help for build
-o, --output string Output file path for the compiled WASM binary (default: <workflow-folder>/binary.wasm)
--skip-type-checks Skip TypeScript project typecheck during compilation (passes --skip-type-checks to cre-compile)
```

### Options inherited from parent commands
Expand Down
1 change: 1 addition & 0 deletions docs/cre_workflow_deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cre workflow deploy ./my-workflow
--no-config Deploy without a config file
-o, --output string The output file for the compiled WASM binary encoded in base64 (default "./binary.wasm.br.b64")
-l, --owner-label string Label for the workflow owner (used during auto-link if owner is not already linked)
--skip-type-checks Skip TypeScript project typecheck during compilation (passes --skip-type-checks to cre-compile)
--unsigned If set, the command will either return the raw transaction instead of sending it to the network or execute the second step of secrets operations using a previously generated raw transaction
--wasm string Path to a pre-built WASM binary (skips compilation)
--yes If set, the command will skip the confirmation prompt and proceed with the operation even if it is potentially destructive
Expand Down
1 change: 1 addition & 0 deletions docs/cre_workflow_hash.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cre workflow hash <workflow-folder-path> [optional flags]
-h, --help help for hash
--no-config Hash without a config file
--public_key string Owner address to use for computing the workflow hash. Required when CRE_ETH_PRIVATE_KEY is not set and no workflow-owner-address is configured. Defaults to the address derived from CRE_ETH_PRIVATE_KEY or the workflow-owner-address in project settings.
--skip-type-checks Skip TypeScript project typecheck during compilation (passes --skip-type-checks to cre-compile)
--wasm string Path or URL to a pre-built WASM binary (skips compilation)
```

Expand Down
Loading
Loading