diff --git a/cmd/creinit/creinit.go b/cmd/creinit/creinit.go index 359ba7d0..45abc595 100644 --- a/cmd/creinit/creinit.go +++ b/cmd/creinit/creinit.go @@ -18,17 +18,19 @@ import ( "github.com/smartcontractkit/cre-cli/internal/settings" "github.com/smartcontractkit/cre-cli/internal/templateconfig" "github.com/smartcontractkit/cre-cli/internal/templaterepo" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" "github.com/smartcontractkit/cre-cli/internal/ui" "github.com/smartcontractkit/cre-cli/internal/validation" ) type Inputs struct { - ProjectName string `validate:"omitempty,project_name" cli:"project-name"` - TemplateName string `validate:"omitempty" cli:"template"` - WorkflowName string `validate:"omitempty,workflow_name" cli:"workflow-name"` - RpcURLs map[string]string // chain-name -> url, from --rpc-url flags - NonInteractive bool - ProjectRoot string // from -R / --project-root flag + ProjectName string `validate:"omitempty,project_name" cli:"project-name"` + TemplateName string `validate:"omitempty" cli:"template"` + WorkflowName string `validate:"omitempty,workflow_name" cli:"workflow-name"` + RpcURLs map[string]string // chain-name -> url, from --rpc-url flags + DeploymentRegistry string // from --deployment-registry flag + NonInteractive bool + ProjectRoot string // from -R / --project-root flag } func New(runtimeContext *runtime.Context) *cobra.Command { @@ -75,6 +77,7 @@ Templates are fetched dynamically from GitHub repositories.`, initCmd.Flags().StringP("template", "t", "", "Name of the template to use (e.g., kv-store-go)") initCmd.Flags().Bool("refresh", false, "Bypass template cache and fetch fresh data") initCmd.Flags().StringArray("rpc-url", nil, "RPC URL for a network (format: chain-name=url, repeatable)") + initCmd.Flags().String("deployment-registry", "", "Registry ID to deploy workflows to (e.g. my-private-registry or onchain:ethereum-testnet-sepolia)") // Deprecated: --template-id is kept for backwards compatibility, maps to hello-world-go initCmd.Flags().Uint32("template-id", 0, "") @@ -141,11 +144,12 @@ func (h *handler) ResolveInputs(v *viper.Viper) (Inputs, error) { } return Inputs{ - ProjectName: v.GetString("project-name"), - TemplateName: templateName, - WorkflowName: v.GetString("workflow-name"), - RpcURLs: rpcURLs, - NonInteractive: v.GetBool("non-interactive"), + ProjectName: v.GetString("project-name"), + TemplateName: templateName, + WorkflowName: v.GetString("workflow-name"), + RpcURLs: rpcURLs, + DeploymentRegistry: v.GetString("deployment-registry"), + NonInteractive: v.GetBool("non-interactive"), }, nil } @@ -270,7 +274,18 @@ func (h *handler) Execute(inputs Inputs) error { } // Run the interactive wizard - result, err := RunWizard(inputs, isNewProject, startDir, workflowTemplates, selectedTemplate) + if inputs.NonInteractive && inputs.DeploymentRegistry == "" { + ui.Warning("No --deployment-registry specified, defaulting to on-chain registry. Add --deployment-registry to make this explicit.") + } + + var registries []*tenantctx.Registry + if h.runtimeContext.TenantContext != nil { + registries = h.runtimeContext.TenantContext.Registries + } + if len(registries) == 0 && !inputs.NonInteractive { + ui.Warning("No registries found for your organization — skipping registry selection. Run `cre registry list` to check.") + } + result, err := RunWizard(inputs, isNewProject, startDir, workflowTemplates, selectedTemplate, registries) if err != nil { // If stdin is not a terminal, the wizard will fail trying to open a TTY. // Detect this via term.IsTerminal rather than matching third-party error strings. @@ -301,6 +316,7 @@ func (h *handler) Execute(inputs Inputs) error { // Extract values from wizard result projName := result.ProjectName workflowName := result.WorkflowName + deploymentRegistry := result.RegistryID // Apply defaults if projName == "" { @@ -413,7 +429,7 @@ func (h *handler) Execute(inputs Inputs) error { h.log.Debug().Msgf("Skipping workflow.yaml generation for %s (already exists from template)", wf.Dir) continue } - if _, err := settings.GenerateWorkflowSettingsFile(wfDir, wf.Dir, entryPoint); err != nil { + if _, err := settings.GenerateWorkflowSettingsFile(wfDir, wf.Dir, entryPoint, deploymentRegistry); err != nil { return fmt.Errorf("failed to generate workflow settings for %s: %w", wf.Dir, err) } } @@ -422,7 +438,7 @@ func (h *handler) Execute(inputs Inputs) error { wfSettingsPath := filepath.Join(workflowDirectory, constants.DefaultWorkflowSettingsFileName) if _, err := os.Stat(wfSettingsPath); err == nil { h.log.Debug().Msgf("Skipping workflow.yaml generation (already exists from template)") - } else if _, err := settings.GenerateWorkflowSettingsFile(workflowDirectory, workflowName, entryPoint); err != nil { + } else if _, err := settings.GenerateWorkflowSettingsFile(workflowDirectory, workflowName, entryPoint, deploymentRegistry); err != nil { return fmt.Errorf("failed to generate %s file: %w", constants.DefaultWorkflowSettingsFileName, err) } } diff --git a/cmd/creinit/creinit_test.go b/cmd/creinit/creinit_test.go index cc9b3281..ea33988e 100644 --- a/cmd/creinit/creinit_test.go +++ b/cmd/creinit/creinit_test.go @@ -416,10 +416,11 @@ func TestInitExecuteFlows(t *testing.T) { defer restoreCwd() inputs := Inputs{ - ProjectName: tc.projectNameFlag, - TemplateName: tc.templateNameFlag, - WorkflowName: tc.workflowNameFlag, - RpcURLs: tc.rpcURLs, + ProjectName: tc.projectNameFlag, + TemplateName: tc.templateNameFlag, + WorkflowName: tc.workflowNameFlag, + RpcURLs: tc.rpcURLs, + DeploymentRegistry: "private", } ctx := sim.NewRuntimeContext() @@ -451,10 +452,11 @@ func TestInsideExistingProjectAddsWorkflow(t *testing.T) { _ = os.Remove(constants.DefaultEnvFileName) inputs := Inputs{ - ProjectName: "", - TemplateName: "test-go", - WorkflowName: "wf-inside-existing-project", - RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + ProjectName: "", + TemplateName: "test-go", + WorkflowName: "wf-inside-existing-project", + RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -483,9 +485,10 @@ func TestInitWithTypescriptTemplateSkipsGoScaffold(t *testing.T) { defer restoreCwd() inputs := Inputs{ - ProjectName: "tsProj", - TemplateName: "test-ts", - WorkflowName: "ts-workflow-01", + ProjectName: "tsProj", + TemplateName: "test-ts", + WorkflowName: "ts-workflow-01", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -514,14 +517,16 @@ func TestInitWithRpcUrlFlags(t *testing.T) { require.NoError(t, err) defer restoreCwd() + rpcURLs := map[string]string{ + "ethereum-testnet-sepolia": "https://sepolia.example.com", + "ethereum-mainnet": "https://mainnet.example.com", + } inputs := Inputs{ - ProjectName: "rpcProj", - TemplateName: "test-multichain", - WorkflowName: "rpc-workflow", - RpcURLs: map[string]string{ - "ethereum-testnet-sepolia": "https://sepolia.example.com", - "ethereum-mainnet": "https://mainnet.example.com", - }, + ProjectName: "rpcProj", + TemplateName: "test-multichain", + WorkflowName: "rpc-workflow", + RpcURLs: rpcURLs, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -554,9 +559,10 @@ func TestInitNoNetworksFallsBackToDefault(t *testing.T) { // Built-in template has no project.yaml from scaffold, // so the CLI generates one with default networks. inputs := Inputs{ - ProjectName: "defaultProj", - TemplateName: "hello-world-go", - WorkflowName: "default-wf", + ProjectName: "defaultProj", + TemplateName: "hello-world-go", + WorkflowName: "default-wf", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -583,9 +589,10 @@ func TestInitRemoteTemplateKeepsProjectYAML(t *testing.T) { // Remote template (test-ts) has no Networks — mock creates project.yaml with default chain. // CLI should preserve the template's project.yaml (no patching needed since no user RPCs). inputs := Inputs{ - ProjectName: "remoteProj", - TemplateName: "test-ts", - WorkflowName: "ts-wf", + ProjectName: "remoteProj", + TemplateName: "test-ts", + WorkflowName: "ts-wf", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -625,6 +632,7 @@ func TestInitProjectDirTemplateRpcPatching(t *testing.T) { "ethereum-testnet-sepolia": "https://sepolia.custom.com", "ethereum-mainnet": "https://mainnet.custom.com", }, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -655,9 +663,10 @@ func TestTemplateNotFound(t *testing.T) { defer restoreCwd() inputs := Inputs{ - ProjectName: "proj", - TemplateName: "nonexistent-template", - WorkflowName: "wf", + ProjectName: "proj", + TemplateName: "nonexistent-template", + WorkflowName: "wf", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -679,10 +688,11 @@ func TestMultiWorkflowNoRename(t *testing.T) { // Multi-workflow template: no --workflow-name needed, dirs stay as declared inputs := Inputs{ - ProjectName: "multiProj", - TemplateName: "bring-your-own-data-go", - WorkflowName: "", - RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + ProjectName: "multiProj", + TemplateName: "bring-your-own-data-go", + WorkflowName: "", + RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -713,10 +723,11 @@ func TestMultiWorkflowIgnoresWorkflowNameFlag(t *testing.T) { // Multi-workflow with --workflow-name flag: flag should be ignored inputs := Inputs{ - ProjectName: "multiProj2", - TemplateName: "bring-your-own-data-go", - WorkflowName: "test-rename", - RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + ProjectName: "multiProj2", + TemplateName: "bring-your-own-data-go", + WorkflowName: "test-rename", + RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -748,9 +759,10 @@ func TestSingleWorkflowDefaultFromTemplate(t *testing.T) { // Note: We must provide a workflow name to avoid the TTY prompt in tests. // Instead, we verify the default logic by providing it explicitly. inputs := Inputs{ - ProjectName: "singleProj", - TemplateName: "kv-store-go", - WorkflowName: "my-workflow", // same as template's workflows[0].dir + ProjectName: "singleProj", + TemplateName: "kv-store-go", + WorkflowName: "my-workflow", // same as template's workflows[0].dir + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -797,9 +809,10 @@ func TestSingleWorkflowRenameWithFlag(t *testing.T) { // Single workflow with --workflow-name: should rename to user's choice inputs := Inputs{ - ProjectName: "renameProj", - TemplateName: "kv-store-go", - WorkflowName: "counter", + ProjectName: "renameProj", + TemplateName: "kv-store-go", + WorkflowName: "counter", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -827,9 +840,10 @@ func TestBuiltInTemplateBackwardsCompat(t *testing.T) { // Built-in template has no Workflows field — should use existing heuristic inputs := Inputs{ - ProjectName: "builtinProj", - TemplateName: "hello-world-go", - WorkflowName: "hello-wf", + ProjectName: "builtinProj", + TemplateName: "hello-world-go", + WorkflowName: "hello-wf", + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -906,11 +920,12 @@ func TestNonInteractiveMissingFlags(t *testing.T) { defer restoreCwd() inputs := Inputs{ - ProjectName: "proj", - TemplateName: "test-multichain", - WorkflowName: "", - NonInteractive: true, - RpcURLs: map[string]string{}, + ProjectName: "proj", + TemplateName: "test-multichain", + WorkflowName: "", + NonInteractive: true, + RpcURLs: map[string]string{}, + DeploymentRegistry: "private", } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -930,10 +945,11 @@ func TestNonInteractiveAllFlagsProvided(t *testing.T) { defer restoreCwd() inputs := Inputs{ - ProjectName: "niProj", - TemplateName: "hello-world-go", - WorkflowName: "my-wf", - NonInteractive: true, + ProjectName: "niProj", + TemplateName: "hello-world-go", + WorkflowName: "my-wf", + DeploymentRegistry: "onchain:ethereum-testnet-sepolia", + NonInteractive: true, } h := newHandlerWithRegistry(sim.NewRuntimeContext(), newMockRegistry()) @@ -958,11 +974,12 @@ func TestInitRespectsProjectRootFlag(t *testing.T) { targetDir := t.TempDir() inputs := Inputs{ - ProjectName: "myproj", - TemplateName: "test-go", - WorkflowName: "mywf", - RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, - ProjectRoot: targetDir, + ProjectName: "myproj", + TemplateName: "test-go", + WorkflowName: "mywf", + RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + ProjectRoot: targetDir, + DeploymentRegistry: "private", } ctx := sim.NewRuntimeContext() @@ -1003,11 +1020,12 @@ func TestInitProjectRootFlagFindsExistingProject(t *testing.T) { )) inputs := Inputs{ - ProjectName: "", - TemplateName: "test-go", - WorkflowName: "new-workflow", - RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, - ProjectRoot: existingProject, + ProjectName: "", + TemplateName: "test-go", + WorkflowName: "new-workflow", + RpcURLs: map[string]string{"ethereum-testnet-sepolia": "https://rpc.example.com"}, + ProjectRoot: existingProject, + DeploymentRegistry: "private", } ctx := sim.NewRuntimeContext() diff --git a/cmd/creinit/wizard.go b/cmd/creinit/wizard.go index ebd0b399..b7fad63c 100644 --- a/cmd/creinit/wizard.go +++ b/cmd/creinit/wizard.go @@ -16,6 +16,7 @@ import ( "github.com/smartcontractkit/cre-cli/internal/constants" "github.com/smartcontractkit/cre-cli/internal/rpc" "github.com/smartcontractkit/cre-cli/internal/templaterepo" + "github.com/smartcontractkit/cre-cli/internal/tenantctx" "github.com/smartcontractkit/cre-cli/internal/ui" "github.com/smartcontractkit/cre-cli/internal/validation" ) @@ -85,6 +86,21 @@ func (f languageFilter) next() languageFilter { } } +// sortRegistries sorts registries: private (off-chain) first, everything else after. +func sortRegistries(registries []*tenantctx.Registry) []*tenantctx.Registry { + priority := func(r *tenantctx.Registry) int { + if strings.EqualFold(r.Type, "off-chain") { + return 0 + } + return 1 + } + sorted := slices.Clone(registries) + slices.SortStableFunc(sorted, func(a, b *tenantctx.Registry) int { + return priority(a) - priority(b) + }) + return sorted +} + // sortTemplates sorts templates: built-in first, then by kind, then alphabetical by title. func sortTemplates(templates []templaterepo.TemplateSummary) []templaterepo.TemplateSummary { sorted := slices.Clone(templates) @@ -257,6 +273,7 @@ const ( stepTemplateConfirm stepNetworkRPCs stepWorkflowName + stepRegistry stepDone ) @@ -290,6 +307,12 @@ type wizardModel struct { // Pre-provided RPC URLs from flags flagRpcURLs map[string]string + // Registry selection + registries []*tenantctx.Registry + registryCursor int + selectedRegistryID string + skipRegistry bool + // Flags to skip steps skipProjectName bool skipTemplate bool @@ -327,12 +350,13 @@ type WizardResult struct { WorkflowName string SelectedTemplate *templaterepo.TemplateSummary NetworkRPCs map[string]string // chain-name -> rpc-url + RegistryID string // selected deployment-registry ID OverwriteDir bool // user confirmed directory overwrite in wizard Completed bool Cancelled bool } -func newWizardModel(inputs Inputs, isNewProject bool, startDir string, templates []templaterepo.TemplateSummary, preselected *templaterepo.TemplateSummary) wizardModel { +func newWizardModel(inputs Inputs, isNewProject bool, startDir string, templates []templaterepo.TemplateSummary, preselected *templaterepo.TemplateSummary, registries []*tenantctx.Registry) wizardModel { // Project name input pi := textinput.New() pi.Placeholder = constants.DefaultProjectName @@ -373,6 +397,7 @@ func newWizardModel(inputs Inputs, isNewProject bool, startDir string, templates flagRpcURLs: flagRPCs, startDir: startDir, isNewProject: isNewProject, + registries: registries, // Styles logoStyle: lipgloss.NewStyle().Foreground(lipgloss.Color(ui.ColorBlue500)).Bold(true), @@ -407,6 +432,16 @@ func newWizardModel(inputs Inputs, isNewProject bool, startDir string, templates m.skipWorkflowName = true } + if inputs.DeploymentRegistry != "" { + m.selectedRegistryID = inputs.DeploymentRegistry + m.skipRegistry = true + } else if len(registries) == 0 { + m.skipRegistry = true + } else { + // Sort: off-chain (private) registries first so the default cursor lands on them. + m.registries = sortRegistries(registries) + } + // Start at the right step m.advanceToNextStep() @@ -508,6 +543,12 @@ func (m *wizardModel) advanceToNextStep() { } m.workflowInput.Focus() return + case stepRegistry: + if m.skipRegistry { + m.step++ + continue + } + return case stepDone: m.completed = true return @@ -618,6 +659,27 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + if m.step == stepRegistry { + switch msg.String() { + case "ctrl+c", "esc": + m.cancelled = true + return m, tea.Quit + case "up", "k": + if m.registryCursor > 0 { + m.registryCursor-- + } + return m, nil + case "down", "j": + if m.registryCursor < len(m.registries)-1 { + m.registryCursor++ + } + return m, nil + case "enter": + return m.handleEnter() + } + return m, nil + } + switch msg.String() { case "ctrl+c", "esc": m.cancelled = true @@ -641,9 +703,7 @@ func (m wizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case stepTemplate: // Forward non-key messages (e.g. FilterMatchesMsg) to the list m.templateList, cmd = m.templateList.Update(msg) - case stepTemplateConfirm: - // Nothing to update - case stepDone: + case stepTemplateConfirm, stepRegistry, stepDone: // Nothing to update } @@ -741,6 +801,13 @@ func (m wizardModel) handleEnter(msgs ...tea.Msg) (tea.Model, tea.Cmd) { m.step++ m.advanceToNextStep() + case stepRegistry: + if m.registryCursor < len(m.registries) { + m.selectedRegistryID = m.registries[m.registryCursor].ID + } + m.step++ + m.advanceToNextStep() + case stepDone: // Already done } @@ -931,6 +998,28 @@ func (m wizardModel) View() string { } } + case stepRegistry: + b.WriteString(m.promptStyle.Render(" Deployment registry")) + b.WriteString("\n") + b.WriteString(m.dimStyle.Render(" Private registries are hosted by Chainlink — no gas needed, ideal for quick testing.")) + b.WriteString("\n") + b.WriteString(m.dimStyle.Render(" On-chain registries require gas and a Web3 key — ideal for production and multisig ownership.")) + b.WriteString("\n\n") + for i, reg := range m.registries { + regType := reg.Type + label := reg.Label + if label == "" { + label = reg.ID + } + typeTag := m.tagStyle.Render("[" + regType + "]") + if i == m.registryCursor { + cursor := m.cursorStyle.Render(">") + fmt.Fprintf(&b, " %s %s %s\n", cursor, typeTag, m.selectedStyle.Render(label)) + } else { + fmt.Fprintf(&b, " %s %s\n", typeTag, m.dimStyle.Render(label)) + } + } + case stepDone: // Nothing to render } @@ -967,6 +1056,7 @@ func (m wizardModel) Result() WizardResult { WorkflowName: m.workflowName, SelectedTemplate: m.selectedTemplate, NetworkRPCs: m.networkRPCs, + RegistryID: m.selectedRegistryID, OverwriteDir: m.overwriteDir, Completed: m.completed, Cancelled: m.cancelled, @@ -974,8 +1064,8 @@ func (m wizardModel) Result() WizardResult { } // RunWizard runs the interactive wizard and returns the result. -func RunWizard(inputs Inputs, isNewProject bool, startDir string, templates []templaterepo.TemplateSummary, preselected *templaterepo.TemplateSummary) (WizardResult, error) { - m := newWizardModel(inputs, isNewProject, startDir, templates, preselected) +func RunWizard(inputs Inputs, isNewProject bool, startDir string, templates []templaterepo.TemplateSummary, preselected *templaterepo.TemplateSummary, registries []*tenantctx.Registry) (WizardResult, error) { + m := newWizardModel(inputs, isNewProject, startDir, templates, preselected, registries) // Check if all steps are skipped if m.completed { diff --git a/docs/cre_init.md b/docs/cre_init.md index a44d4698..ff95f14d 100644 --- a/docs/cre_init.md +++ b/docs/cre_init.md @@ -18,12 +18,13 @@ cre init [optional flags] ### Options ``` - -h, --help help for init - -p, --project-name string Name for the new project - --refresh Bypass template cache and fetch fresh data - --rpc-url stringArray RPC URL for a network (format: chain-name=url, repeatable) - -t, --template string Name of the template to use (e.g., kv-store-go) - -w, --workflow-name string Name for the new workflow + --deployment-registry string Registry ID to deploy workflows to (e.g. my-private-registry or onchain:ethereum-testnet-sepolia) + -h, --help help for init + -p, --project-name string Name for the new project + --refresh Bypass template cache and fetch fresh data + --rpc-url stringArray RPC URL for a network (format: chain-name=url, repeatable) + -t, --template string Name of the template to use (e.g., kv-store-go) + -w, --workflow-name string Name for the new workflow ``` ### Options inherited from parent commands diff --git a/internal/settings/settings_generate.go b/internal/settings/settings_generate.go index 635bec25..7d0d3e37 100644 --- a/internal/settings/settings_generate.go +++ b/internal/settings/settings_generate.go @@ -180,11 +180,16 @@ func FindOrCreateProjectSettings(startDir string, replacements map[string]string return nil } -func GenerateWorkflowSettingsFile(workingDirectory string, workflowName string, workflowPath string) (string, error) { +func GenerateWorkflowSettingsFile(workingDirectory string, workflowName string, workflowPath string, deploymentRegistry string) (string, error) { // Use default replacements. replacements := GetDefaultReplacements() replacements["WorkflowName"] = workflowName replacements["WorkflowPath"] = workflowPath + if deploymentRegistry != "" { + replacements["DeploymentRegistryLine"] = fmt.Sprintf(" deployment-registry: %q", deploymentRegistry) + } else { + replacements["DeploymentRegistryLine"] = "" + } // Resolve the absolute output path for the workflow settings file. outputPath, err := filepath.Abs(path.Join(workingDirectory, constants.DefaultWorkflowSettingsFileName)) diff --git a/internal/settings/template/workflow.yaml.tpl b/internal/settings/template/workflow.yaml.tpl index ae0124b7..6741729a 100644 --- a/internal/settings/template/workflow.yaml.tpl +++ b/internal/settings/template/workflow.yaml.tpl @@ -18,16 +18,18 @@ staging-settings: user-workflow: workflow-name: "{{WorkflowName}}-staging" +{{DeploymentRegistryLine}} workflow-artifacts: workflow-path: "{{WorkflowPath}}" config-path: "{{ConfigPathStaging}}" secrets-path: "{{SecretsPath}}" - + # ========================================================================== production-settings: user-workflow: workflow-name: "{{WorkflowName}}-production" +{{DeploymentRegistryLine}} workflow-artifacts: workflow-path: "{{WorkflowPath}}" config-path: "{{ConfigPathProduction}}" diff --git a/test/init_and_binding_generation_and_simulate_go_test.go b/test/init_and_binding_generation_and_simulate_go_test.go index 53cf7a60..73fb3a13 100644 --- a/test/init_and_binding_generation_and_simulate_go_test.go +++ b/test/init_and_binding_generation_and_simulate_go_test.go @@ -37,6 +37,7 @@ func TestE2EInit_DevPoRTemplate(t *testing.T) { "--project-name", projectName, "--template", templateName, "--workflow-name", workflowName, + "--deployment-registry", "private", } var stdout, stderr bytes.Buffer initCmd := exec.Command(CLIPath, initArgs...) diff --git a/test/init_and_simulate_ts_test.go b/test/init_and_simulate_ts_test.go index d0e4a0e4..5f51c16f 100644 --- a/test/init_and_simulate_ts_test.go +++ b/test/init_and_simulate_ts_test.go @@ -36,6 +36,7 @@ func TestE2EInit_DevPoRTemplateTS(t *testing.T) { "--project-name", projectName, "--template", templateName, "--workflow-name", workflowName, + "--deployment-registry", "private", } var stdout, stderr bytes.Buffer initCmd := exec.Command(CLIPath, initArgs...) diff --git a/test/init_convert_simulate_go_test.go b/test/init_convert_simulate_go_test.go index ab7c2f96..b644053d 100644 --- a/test/init_convert_simulate_go_test.go +++ b/test/init_convert_simulate_go_test.go @@ -39,6 +39,7 @@ func TestE2EInit_ConvertToCustomBuild_Go(t *testing.T) { "--project-name", projectName, "--template-id", templateID, "--workflow-name", workflowName, + "--deployment-registry", "private", ) initCmd.Dir = tempDir initCmd.Stdout = &stdout diff --git a/test/init_convert_simulate_ts_test.go b/test/init_convert_simulate_ts_test.go index 1a552f7b..e2834d64 100644 --- a/test/init_convert_simulate_ts_test.go +++ b/test/init_convert_simulate_ts_test.go @@ -40,6 +40,7 @@ func TestE2EInit_ConvertToCustomBuild_TS(t *testing.T) { "--project-name", projectName, "--template-id", templateID, "--workflow-name", workflowName, + "--deployment-registry", "private", ) initCmd.Dir = tempDir initCmd.Stdout = &stdout diff --git a/test/multi_command_flows/workflow_happy_path_3.go b/test/multi_command_flows/workflow_happy_path_3.go index 5e125e74..d79ad7e7 100644 --- a/test/multi_command_flows/workflow_happy_path_3.go +++ b/test/multi_command_flows/workflow_happy_path_3.go @@ -19,8 +19,8 @@ import ( "github.com/smartcontractkit/cre-cli/internal/testutil" ) -// workflowInit runs cre init to initialize a new workflow project from scratch -func workflowInit(t *testing.T, projectRootFlag, projectName, workflowName string) (output string, gqlURL string) { +// workflowInitWithRegistry runs cre init with a specific deployment registry +func workflowInitWithRegistry(t *testing.T, projectRootFlag, projectName, workflowName, registry string) (output string, gqlURL string) { t.Helper() // Set up mock GraphQL server for authentication validation @@ -59,6 +59,7 @@ func workflowInit(t *testing.T, projectRootFlag, projectName, workflowName strin "--project-name", projectName, "--workflow-name", workflowName, "--template", "hello-world-go", // Use the built-in Go template + "--deployment-registry", registry, } cmd := exec.Command(CLIPath, args...) @@ -363,8 +364,8 @@ func RunHappyPath3aWorkflow(t *testing.T, tc TestConfig, projectName, ownerAddre workflowName := "happy-path-3a-workflow" - // Step 1: Initialize new project with workflow - initOut, gqlURL := workflowInit(t, tc.GetProjectRootFlag(), projectName, workflowName) + // Step 1: Initialize new project with workflow using on-chain registry (required for --unsigned) + initOut, gqlURL := workflowInitWithRegistry(t, tc.GetProjectRootFlag(), projectName, workflowName, "onchain:anvil-devnet") require.Contains(t, initOut, "Project created successfully", "expected init to succeed.\nCLI OUTPUT:\n%s", initOut) // Build the project root flag pointing to the newly created project