Skip to content
Open
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
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