From 8755a58e7048cb9604187e5d89b7e256c287a8f4 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Fri, 13 Mar 2026 15:29:47 -0400 Subject: [PATCH 1/3] Add hash command and display hashes during other commands --- cmd/common/hash.go | 12 ++ cmd/root.go | 2 + cmd/workflow/build/build.go | 1 + cmd/workflow/deploy/deploy.go | 4 + cmd/workflow/deploy/prepare.go | 10 +- cmd/workflow/hash/hash.go | 181 ++++++++++++++++++ cmd/workflow/hash/hash_test.go | 248 +++++++++++++++++++++++++ cmd/workflow/simulate/simulate.go | 3 + cmd/workflow/workflow.go | 2 + internal/settings/workflow_settings.go | 2 + 10 files changed, 462 insertions(+), 3 deletions(-) create mode 100644 cmd/common/hash.go create mode 100644 cmd/workflow/hash/hash.go create mode 100644 cmd/workflow/hash/hash_test.go diff --git a/cmd/common/hash.go b/cmd/common/hash.go new file mode 100644 index 00000000..2df5044a --- /dev/null +++ b/cmd/common/hash.go @@ -0,0 +1,12 @@ +package common + +import ( + "crypto/sha256" + "encoding/hex" +) + +// HashBytes computes the SHA-256 hash of data and returns it as a hex string. +func HashBytes(data []byte) string { + h := sha256.Sum256(data) + return hex.EncodeToString(h[:]) +} diff --git a/cmd/root.go b/cmd/root.go index 8aecb3ad..fe2cf42a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -466,6 +466,7 @@ func isLoadCredentials(cmd *cobra.Command) bool { "cre update": {}, "cre workflow": {}, "cre workflow build": {}, + "cre workflow hash": {}, "cre account": {}, "cre secrets": {}, "cre templates": {}, @@ -536,6 +537,7 @@ func shouldShowSpinner(cmd *cobra.Command) bool { "cre update": {}, "cre workflow": {}, // Just shows help "cre workflow build": {}, // Offline command, no async init + "cre workflow hash": {}, // Offline command, has own spinner "cre account": {}, // Just shows help "cre secrets": {}, // Just shows help "cre templates": {}, // Just shows help diff --git a/cmd/workflow/build/build.go b/cmd/workflow/build/build.go index f1d3cc7e..aca1a7bb 100644 --- a/cmd/workflow/build/build.go +++ b/cmd/workflow/build/build.go @@ -64,6 +64,7 @@ func execute(workflowFolder, outputPath string) error { return fmt.Errorf("failed to compile workflow: %w", err) } ui.Success("Workflow compiled successfully") + ui.Dim(fmt.Sprintf("Binary hash: %s", cmdcommon.HashBytes(wasmBytes))) if err := os.WriteFile(outputPath, wasmBytes, 0666); err != nil { //nolint:gosec return fmt.Errorf("failed to write WASM binary: %w", err) diff --git a/cmd/workflow/deploy/deploy.go b/cmd/workflow/deploy/deploy.go index 44581cb8..2fa9e312 100644 --- a/cmd/workflow/deploy/deploy.go +++ b/cmd/workflow/deploy/deploy.go @@ -265,6 +265,10 @@ func (h *handler) Execute(ctx context.Context) error { return fmt.Errorf("failed to prepare workflow artifact: %w", err) } + ui.Dim(fmt.Sprintf("Binary hash: %s", cmdcommon.HashBytes(h.workflowArtifact.RawBinaryForID))) + ui.Dim(fmt.Sprintf("Config hash: %s", cmdcommon.HashBytes(h.workflowArtifact.RawConfigForID))) + ui.Dim(fmt.Sprintf("Workflow hash: %s", h.workflowArtifact.WorkflowID)) + h.runtimeContext.Workflow.ID = h.workflowArtifact.WorkflowID h.wg.Wait() diff --git a/cmd/workflow/deploy/prepare.go b/cmd/workflow/deploy/prepare.go index 7e774277..7ff5bdf8 100644 --- a/cmd/workflow/deploy/prepare.go +++ b/cmd/workflow/deploy/prepare.go @@ -9,9 +9,11 @@ import ( ) type workflowArtifact struct { - BinaryData []byte - ConfigData []byte - WorkflowID string + BinaryData []byte + ConfigData []byte + WorkflowID string + RawBinaryForID []byte + RawConfigForID []byte } func (h *handler) prepareWorkflowBinary() ([]byte, error) { @@ -80,6 +82,8 @@ func (h *handler) PrepareWorkflowArtifact() error { } h.workflowArtifact.WorkflowID = workflowID + h.workflowArtifact.RawBinaryForID = binaryForID + h.workflowArtifact.RawConfigForID = configData return nil } diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go new file mode 100644 index 00000000..03f49224 --- /dev/null +++ b/cmd/workflow/hash/hash.go @@ -0,0 +1,181 @@ +package hash + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + workflowUtils "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + cmdcommon "github.com/smartcontractkit/cre-cli/cmd/common" + "github.com/smartcontractkit/cre-cli/internal/ethkeys" + "github.com/smartcontractkit/cre-cli/internal/runtime" + "github.com/smartcontractkit/cre-cli/internal/settings" + "github.com/smartcontractkit/cre-cli/internal/ui" +) + +type Inputs struct { + ForUser string + WasmPath string + ConfigPath string + WorkflowName string + WorkflowPath string + OwnerFromSettings string + PrivateKey string +} + +func New(runtimeContext *runtime.Context) *cobra.Command { + hashCmd := &cobra.Command{ + Use: "hash ", + Short: "Computes and displays workflow hashes", + Long: `Computes the binary hash, config hash, and workflow hash for a workflow. The workflow hash uses the same algorithm as the on-chain workflow ID.`, + Args: cobra.ExactArgs(1), + Example: ` cre workflow hash ./my-workflow + cre workflow hash ./my-workflow --public_key 0x1234...abcd`, + RunE: func(cmd *cobra.Command, args []string) error { + forUser, _ := cmd.Flags().GetString("public_key") + + s := runtimeContext.Settings + v := runtimeContext.Viper + + rawPrivKey := v.GetString(settings.EthPrivateKeyEnvVar) + + inputs := Inputs{ + ForUser: forUser, + WasmPath: v.GetString("wasm"), + ConfigPath: cmdcommon.ResolveConfigPath(v, s.Workflow.WorkflowArtifactSettings.ConfigPath), + WorkflowName: s.Workflow.UserWorkflowSettings.WorkflowName, + WorkflowPath: s.Workflow.WorkflowArtifactSettings.WorkflowPath, + OwnerFromSettings: s.Workflow.UserWorkflowSettings.WorkflowOwnerAddress, + PrivateKey: settings.NormalizeHexKey(rawPrivKey), + } + + return Execute(inputs) + }, + } + + hashCmd.Flags().String("public_key", "", + "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.") + hashCmd.Flags().String("wasm", "", "Path or URL to a pre-built WASM binary (skips compilation)") + hashCmd.Flags().String("config", "", "Override the config file path from workflow.yaml") + 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") + + return hashCmd +} + +func Execute(inputs Inputs) error { + binary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath) + if err != nil { + return err + } + + config, err := loadConfig(inputs.ConfigPath) + if err != nil { + return err + } + + ownerAddress, err := ResolveOwner(inputs.ForUser, inputs.OwnerFromSettings, inputs.PrivateKey) + if err != nil { + return err + } + + binaryHash := cmdcommon.HashBytes(binary) + configHash := cmdcommon.HashBytes(config) + + workflowID, err := workflowUtils.GenerateWorkflowIDFromStrings(ownerAddress, inputs.WorkflowName, binary, config, "") + if err != nil { + return fmt.Errorf("failed to generate workflow hash: %w", err) + } + + ui.Dim(fmt.Sprintf("Binary hash: %s", binaryHash)) + ui.Dim(fmt.Sprintf("Config hash: %s", configHash)) + ui.Dim(fmt.Sprintf("Workflow hash: %s", workflowID)) + + return nil +} + +func ResolveOwner(forUser, ownerFromSettings, privateKey string) (string, error) { + if forUser != "" { + return forUser, nil + } + + if ownerFromSettings != "" { + return ownerFromSettings, nil + } + + if privateKey != "" { + addr, err := ethkeys.DeriveEthAddressFromPrivateKey(privateKey) + if err != nil { + return "", fmt.Errorf("failed to derive owner from private key: %w", err) + } + return addr, nil + } + + 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) { + if wasmFlag != "" { + if cmdcommon.IsURL(wasmFlag) { + ui.Dim("Fetching WASM binary from URL...") + data, err := cmdcommon.FetchURL(wasmFlag) + if err != nil { + return nil, fmt.Errorf("failed to fetch WASM from URL: %w", err) + } + ui.Success("Fetched WASM binary from URL") + return cmdcommon.EnsureRawWasm(data) + } + ui.Dim("Reading pre-built WASM binary...") + data, err := os.ReadFile(wasmFlag) + if err != nil { + return nil, fmt.Errorf("failed to read WASM binary: %w", err) + } + ui.Success(fmt.Sprintf("Loaded WASM binary from %s", wasmFlag)) + return cmdcommon.EnsureRawWasm(data) + } + + workflowDir, err := os.Getwd() + if err != nil { + return nil, fmt.Errorf("workflow directory: %w", err) + } + resolvedWorkflowPath, err := cmdcommon.ResolveWorkflowPath(workflowDir, workflowPathFromSettings) + if err != nil { + return nil, fmt.Errorf("workflow path: %w", err) + } + + spinner := ui.NewSpinner() + spinner.Start("Compiling workflow...") + wasmBytes, err := cmdcommon.CompileWorkflowToWasm(resolvedWorkflowPath) + spinner.Stop() + if err != nil { + ui.Error("Build failed:") + return nil, fmt.Errorf("failed to compile workflow: %w", err) + } + ui.Success("Workflow compiled") + + return wasmBytes, nil +} + +func loadConfig(configPath string) ([]byte, error) { + if configPath == "" { + return nil, nil + } + if cmdcommon.IsURL(configPath) { + ui.Dim("Fetching config from URL...") + data, err := cmdcommon.FetchURL(configPath) + if err != nil { + return nil, fmt.Errorf("failed to fetch config from URL: %w", err) + } + return data, nil + } + data, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + return data, nil +} diff --git a/cmd/workflow/hash/hash_test.go b/cmd/workflow/hash/hash_test.go new file mode 100644 index 00000000..6587ba2e --- /dev/null +++ b/cmd/workflow/hash/hash_test.go @@ -0,0 +1,248 @@ +package hash + +import ( + "crypto/sha256" + "encoding/hex" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + workflowUtils "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + cmdcommon "github.com/smartcontractkit/cre-cli/cmd/common" +) + +// Well-known test private key (never use on a real network). +const testPrivateKey = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + +// Address derived from testPrivateKey. +const testDerivedAddress = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + +func TestResolveOwner_WithForUser(t *testing.T) { + t.Parallel() + addr, err := ResolveOwner("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "", "") + require.NoError(t, err) + assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", addr) +} + +func TestResolveOwner_WithForUserOverridesAll(t *testing.T) { + t.Parallel() + addr, err := ResolveOwner("0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "0xOtherAddress", testPrivateKey) + require.NoError(t, err) + assert.Equal(t, "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", addr, + "--public_key should take priority over settings and private key") +} + +func TestResolveOwner_FromSettings(t *testing.T) { + t.Parallel() + addr, err := ResolveOwner("", "0xSettingsOwner", "") + require.NoError(t, err) + assert.Equal(t, "0xSettingsOwner", addr) +} + +func TestResolveOwner_FromPrivateKey(t *testing.T) { + t.Parallel() + addr, err := ResolveOwner("", "", testPrivateKey) + require.NoError(t, err) + assert.Equal(t, testDerivedAddress, addr) +} + +func TestResolveOwner_NothingProvided(t *testing.T) { + t.Parallel() + _, err := ResolveOwner("", "", "") + require.Error(t, err) + assert.Contains(t, err.Error(), "--public_key") +} + +func TestResolveOwner_InvalidPrivateKey(t *testing.T) { + t.Parallel() + _, err := ResolveOwner("", "", "not-a-valid-key") + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to derive owner") +} + +func TestExecute_WithForUser(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + ForUser: "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + } + + err := Execute(inputs) + require.NoError(t, err) +} + +func TestExecute_WithoutForUser_UsesPrivateKey(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + PrivateKey: testPrivateKey, + } + + err := Execute(inputs) + require.NoError(t, err) +} + +func TestExecute_WithoutForUser_NoKey_Errors(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + } + + err := Execute(inputs) + require.Error(t, err) + assert.Contains(t, err.Error(), "--public_key") +} + +func TestExecute_HashesAreDeterministic(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + inputs := Inputs{ + ForUser: "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + WasmPath: wasmFile, + ConfigPath: configFile, + WorkflowName: "test-workflow", + } + + wasmBytes, err := os.ReadFile(wasmFile) + require.NoError(t, err) + configBytes, err := os.ReadFile(configFile) + require.NoError(t, err) + + expectedBinaryHash := cmdcommon.HashBytes(wasmBytes) + expectedConfigHash := cmdcommon.HashBytes(configBytes) + expectedWorkflowID, err := workflowUtils.GenerateWorkflowIDFromStrings( + inputs.ForUser, inputs.WorkflowName, wasmBytes, configBytes, "") + require.NoError(t, err) + + // Verify the individual hash computations are as expected (SHA-256) + binarySum := sha256.Sum256(wasmBytes) + assert.Equal(t, hex.EncodeToString(binarySum[:]), expectedBinaryHash) + + configSum := sha256.Sum256(configBytes) + assert.Equal(t, hex.EncodeToString(configSum[:]), expectedConfigHash) + + // Workflow ID should start with "00" (version byte) + assert.True(t, strings.HasPrefix(expectedWorkflowID, "00"), + "workflow ID should start with version byte 00") + + // Running Execute should succeed (hashes are printed via ui, verified above) + err = Execute(inputs) + require.NoError(t, err) +} + +func TestExecute_EmptyConfig(t *testing.T) { + wasmFile, _ := setupTestArtifacts(t) + + inputs := Inputs{ + ForUser: "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", + WasmPath: wasmFile, + ConfigPath: "", + WorkflowName: "test-workflow", + } + + err := Execute(inputs) + require.NoError(t, err) +} + +func TestExecute_DifferentOwnersProduceDifferentWorkflowHashes(t *testing.T) { + wasmFile, configFile := setupTestArtifacts(t) + + wasmBytes, err := os.ReadFile(wasmFile) + require.NoError(t, err) + configBytes, err := os.ReadFile(configFile) + require.NoError(t, err) + + id1, err := workflowUtils.GenerateWorkflowIDFromStrings( + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", "test-workflow", wasmBytes, configBytes, "") + require.NoError(t, err) + + id2, err := workflowUtils.GenerateWorkflowIDFromStrings( + "0x1111111111111111111111111111111111111111", "test-workflow", wasmBytes, configBytes, "") + require.NoError(t, err) + + assert.NotEqual(t, id1, id2, "different owners should produce different workflow hashes") +} + +func TestHashCommandArgs(t *testing.T) { + t.Parallel() + tests := []struct { + name string + args []string + wantErr string + }{ + { + name: "no args provided", + args: []string{}, + wantErr: "accepts 1 arg(s), received 0", + }, + { + name: "too many args", + args: []string{"path1", "path2"}, + wantErr: "accepts 1 arg(s), received 2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + cmd := New(nil) + cmd.SetArgs(tt.args) + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + err := cmd.Execute() + require.Error(t, err) + assert.ErrorContains(t, err, tt.wantErr) + }) + } +} + +func TestHashCommandFlags(t *testing.T) { + t.Parallel() + cmd := New(nil) + + f := cmd.Flags().Lookup("public_key") + require.NotNil(t, f, "public_key flag should exist") + assert.Equal(t, "", f.DefValue) + assert.Contains(t, f.Usage, "Required when CRE_ETH_PRIVATE_KEY is not set") + assert.Contains(t, f.Usage, "Defaults to") + + f = cmd.Flags().Lookup("wasm") + require.NotNil(t, f, "wasm flag should exist") + + f = cmd.Flags().Lookup("config") + require.NotNil(t, f, "config flag should exist") + + f = cmd.Flags().Lookup("no-config") + require.NotNil(t, f, "no-config flag should exist") +} + +// setupTestArtifacts creates a minimal WASM file and config file in a temp directory. +func setupTestArtifacts(t *testing.T) (wasmPath, configPath string) { + t.Helper() + dir := t.TempDir() + + // Minimal valid WASM binary (magic + version) + wasmMagic := []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00} + wasmPath = filepath.Join(dir, "test.wasm") + require.NoError(t, os.WriteFile(wasmPath, wasmMagic, 0600)) + + configData := []byte(`workflowName: "test"`) + configPath = filepath.Join(dir, "config.yml") + require.NoError(t, os.WriteFile(configPath, configData, 0600)) + + return wasmPath, configPath +} diff --git a/cmd/workflow/simulate/simulate.go b/cmd/workflow/simulate/simulate.go index f5ce48ff..3502b69f 100644 --- a/cmd/workflow/simulate/simulate.go +++ b/cmd/workflow/simulate/simulate.go @@ -349,6 +349,9 @@ func (h *handler) Execute(inputs Inputs) error { } } + ui.Dim(fmt.Sprintf("Binary hash: %s", cmdcommon.HashBytes(wasmFileBinary))) + ui.Dim(fmt.Sprintf("Config hash: %s", cmdcommon.HashBytes(config))) + // Read the secrets file var secrets []byte if inputs.SecretsPath != "" { diff --git a/cmd/workflow/workflow.go b/cmd/workflow/workflow.go index f9863980..844b40d3 100644 --- a/cmd/workflow/workflow.go +++ b/cmd/workflow/workflow.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/cre-cli/cmd/workflow/convert" "github.com/smartcontractkit/cre-cli/cmd/workflow/delete" "github.com/smartcontractkit/cre-cli/cmd/workflow/deploy" + "github.com/smartcontractkit/cre-cli/cmd/workflow/hash" "github.com/smartcontractkit/cre-cli/cmd/workflow/pause" "github.com/smartcontractkit/cre-cli/cmd/workflow/simulate" "github.com/smartcontractkit/cre-cli/cmd/workflow/test" @@ -28,6 +29,7 @@ func New(runtimeContext *runtime.Context) *cobra.Command { workflowCmd.AddCommand(pause.New(runtimeContext)) workflowCmd.AddCommand(test.New(runtimeContext)) workflowCmd.AddCommand(deploy.New(runtimeContext)) + workflowCmd.AddCommand(hash.New(runtimeContext)) workflowCmd.AddCommand(simulate.New(runtimeContext)) return workflowCmd diff --git a/internal/settings/workflow_settings.go b/internal/settings/workflow_settings.go index 0e7b658b..9c15c373 100644 --- a/internal/settings/workflow_settings.go +++ b/internal/settings/workflow_settings.go @@ -253,6 +253,8 @@ func ShouldSkipGetOwner(cmd *cobra.Command) bool { switch cmd.Name() { case "help": return true + case "hash": + return true case "simulate": // Treat missing/invalid flag as false (i.e., skip). // If broadcast is explicitly true, don't skip. From 61f6a21546e95a513f4451d704117edb0c5540cb Mon Sep 17 00:00:00 2001 From: De Clercq Wentzel <10665586+wentzeld@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:05:39 -0700 Subject: [PATCH 2/3] parity between deploy and hash --- cmd/workflow/hash/hash.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/workflow/hash/hash.go b/cmd/workflow/hash/hash.go index 03f49224..a5cf1bee 100644 --- a/cmd/workflow/hash/hash.go +++ b/cmd/workflow/hash/hash.go @@ -27,10 +27,10 @@ type Inputs struct { func New(runtimeContext *runtime.Context) *cobra.Command { hashCmd := &cobra.Command{ - Use: "hash ", - Short: "Computes and displays workflow hashes", - Long: `Computes the binary hash, config hash, and workflow hash for a workflow. The workflow hash uses the same algorithm as the on-chain workflow ID.`, - Args: cobra.ExactArgs(1), + Use: "hash ", + Short: "Computes and displays workflow hashes", + Long: `Computes the binary hash, config hash, and workflow hash for a workflow. The workflow hash uses the same algorithm as the on-chain workflow ID.`, + Args: cobra.ExactArgs(1), Example: ` cre workflow hash ./my-workflow cre workflow hash ./my-workflow --public_key 0x1234...abcd`, RunE: func(cmd *cobra.Command, args []string) error { @@ -69,11 +69,16 @@ func New(runtimeContext *runtime.Context) *cobra.Command { } func Execute(inputs Inputs) error { - binary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath) + rawBinary, err := loadBinary(inputs.WasmPath, inputs.WorkflowPath) if err != nil { return err } + binary, err := cmdcommon.CompressBrotli(rawBinary) + if err != nil { + return fmt.Errorf("failed to compress binary: %w", err) + } + config, err := loadConfig(inputs.ConfigPath) if err != nil { return err From acca84db197d0bcdd3febfcf4734eff0221b3369 Mon Sep 17 00:00:00 2001 From: Ryan Tinianov Date: Mon, 16 Mar 2026 09:18:21 -0400 Subject: [PATCH 3/3] Fix docs --- docs/cre_workflow.md | 1 + docs/cre_workflow_hash.md | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 docs/cre_workflow_hash.md diff --git a/docs/cre_workflow.md b/docs/cre_workflow.md index d2a7fb30..1b220e78 100644 --- a/docs/cre_workflow.md +++ b/docs/cre_workflow.md @@ -34,6 +34,7 @@ cre workflow [optional flags] * [cre workflow custom-build](cre_workflow_custom-build.md) - Converts an existing workflow to a custom (self-compiled) build * [cre workflow delete](cre_workflow_delete.md) - Deletes all versions of a workflow from the Workflow Registry * [cre workflow deploy](cre_workflow_deploy.md) - Deploys a workflow to the Workflow Registry contract +* [cre workflow hash](cre_workflow_hash.md) - Computes and displays workflow hashes * [cre workflow pause](cre_workflow_pause.md) - Pauses workflow on the Workflow Registry contract * [cre workflow simulate](cre_workflow_simulate.md) - Simulates a workflow diff --git a/docs/cre_workflow_hash.md b/docs/cre_workflow_hash.md new file mode 100644 index 00000000..b1abe6bb --- /dev/null +++ b/docs/cre_workflow_hash.md @@ -0,0 +1,44 @@ +## cre workflow hash + +Computes and displays workflow hashes + +### Synopsis + +Computes the binary hash, config hash, and workflow hash for a workflow. The workflow hash uses the same algorithm as the on-chain workflow ID. + +``` +cre workflow hash [optional flags] +``` + +### Examples + +``` + cre workflow hash ./my-workflow + cre workflow hash ./my-workflow --public_key 0x1234...abcd +``` + +### Options + +``` + --config string Override the config file path from workflow.yaml + --default-config Use the config path from workflow.yaml settings (default behavior) + -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. + --wasm string Path or URL to a pre-built WASM binary (skips compilation) +``` + +### Options inherited from parent commands + +``` + -e, --env string Path to .env file which contains sensitive info + -R, --project-root string Path to the project root + -E, --public-env string Path to .env.public file which contains shared, non-sensitive build config + -T, --target string Use target settings from YAML config + -v, --verbose Run command in VERBOSE mode +``` + +### SEE ALSO + +* [cre workflow](cre_workflow.md) - Manages workflows +