Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
a510408
feat: Implement workflow step types with registry pattern (DEV-263, D…
osterman Dec 20, 2025
90eccb5
feat: Add input type and workflow step types with complete TUI suppor…
osterman Dec 20, 2025
01ea21d
refactor: Consolidate success/info/warn/error steps into unified toas…
osterman Dec 20, 2025
eddc755
fix: Render markdown in pager step for .md files
osterman Dec 20, 2025
7ba0f6c
refactor: Address CodeRabbit review feedback for workflow steps
osterman Dec 20, 2025
19a52da
docs: Add separator field to join step and complete step type documen…
osterman Dec 21, 2025
e4cab38
feat: Add alert, title, clear, env, and exit workflow step types
osterman Dec 21, 2025
6edf30e
fix: Update workflow error snapshots to use correct explanation/hint …
osterman Dec 21, 2025
1a5dbe5
feat: Add show configuration, sleep, stage step types for workflows
osterman Dec 22, 2025
43916ef
fix: Correct progress bar rendering to clear line before step output
osterman Dec 22, 2025
8ab21f4
docs: Add workflow step test coverage improvement plan
osterman Dec 22, 2025
4e2fd4b
test: Increase pkg/workflow/step test coverage to 77%
osterman Dec 22, 2025
92ffd25
chore: Fix NOTICE file license URLs
osterman Dec 23, 2025
8d27fd2
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 23, 2025
43dab63
fix: Address CodeRabbit review comments
osterman Dec 23, 2025
0288bb1
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 23, 2025
2224d44
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Dec 23, 2025
0774a73
fix: Make file_test.go cross-platform for Windows
osterman Dec 23, 2025
b38eeb1
docs: Update roadmap with detailed workflow step types
osterman Dec 28, 2025
228bcff
fix: Address CodeRabbit review comments for workflow execution
osterman Dec 28, 2025
7d72e73
fix: Add perf.Track to remaining public functions
osterman Dec 29, 2025
8a777e5
feat: Move step handlers to pkg/runner for unified execution
osterman Jan 3, 2026
ffb7d37
docs: Add step types partial for workflows and custom commands
osterman Jan 3, 2026
2024bc7
fix: Address CodeRabbit and security scanning review comments
osterman Jan 3, 2026
41daf25
docs: Add blog post and update roadmap for custom commands step types
osterman Jan 3, 2026
0a61bf2
chore: Remove duplicate blog post and update roadmap changelog refs
osterman Jan 4, 2026
32bc673
fix: Apply Copilot suggestions for overflow protection
osterman Jan 4, 2026
9c25432
fix: Use min() for cleaner overflow protection
osterman Jan 4, 2026
a0e2b21
fix: Make SpinHandler tests platform-aware for Windows compatibility
osterman Jan 5, 2026
43ae363
fix: Add overflow protection to PrepareEnvironment map allocation
osterman Jan 5, 2026
3497285
fix: Address CodeQL overflow alert and CodeRabbit refactor suggestion
osterman Jan 6, 2026
6c4a46d
fix: Use explicit if-guard for CodeQL overflow detection in log.go
osterman Jan 6, 2026
add5710
fix: Extract safeKeyvalsCapacity helper for CodeQL overflow detection
osterman Jan 8, 2026
f6fb05c
Merge branch 'main' into feature/dev-263-add-input-type-to-atmos-work…
osterman Jan 16, 2026
99b5b57
docs: Rename demo-interactive-workflows to interactive-workflows
osterman Jan 16, 2026
cedd47a
docs: Add custom-commands example with interactive and advanced commands
osterman Jan 16, 2026
fe213af
docs: Update file-browser plugin for renamed examples
osterman Jan 16, 2026
459c8c5
fix: Update test paths for renamed custom-commands example directory
osterman Jan 17, 2026
5f3e0ae
fix: Address CodeRabbit review feedback
osterman Jan 17, 2026
6c6d47b
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 17, 2026
ebd83e0
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Jan 17, 2026
e8c770e
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Jan 17, 2026
21149ce
Merge branch 'main' into feature/dev-263-add-input-type-to-atmos-work…
osterman Jan 17, 2026
7e37a3d
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 17, 2026
a4fb0c4
fix: Address CodeRabbit review comments for workflow execution
osterman Jan 18, 2026
acdd19a
Merge branch 'main' into feature/dev-263-add-input-type-to-atmos-work…
osterman Jan 24, 2026
f09b55a
feat: Add viewport dimension constraints to pager
osterman Feb 27, 2026
39942e3
Merge branch 'main' into feature/dev-263-add-input-type-to-atmos-work…
osterman Feb 27, 2026
1df1b46
Merge remote-tracking branch 'origin/main' into feature/dev-263-add-i…
osterman May 30, 2026
619a845
style: apply gofumpt formatting to pkg/pager
osterman May 30, 2026
6fe1336
docs(prd): add built-in command override design; correct stale overri…
osterman May 30, 2026
aedcc8a
Merge branch 'main' into feature/dev-263-add-input-type-to-atmos-work…
aknysh May 30, 2026
a950ba9
docs(prd): clarify nested-collision and invoke schema; add step Execu…
aknysh May 30, 2026
0a183d3
test(workflow): raise step-type patch coverage above 80%
osterman May 30, 2026
3c10baf
updates
aknysh May 30, 2026
13f468e
Merge remote-tracking branch 'origin/feature/dev-263-add-input-type-t…
aknysh May 30, 2026
2359e2d
test: skip interactive Execute TTY test when stdin/stdout are TTYs
aknysh May 30, 2026
72356da
fix(workflow): only print step label when show.count is enabled
osterman May 30, 2026
5f1f6ad
docs(prd): remove invoke field from WorkflowStep schema
osterman May 30, 2026
d92c2b5
updates
aknysh May 30, 2026
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
61 changes: 53 additions & 8 deletions cmd/cmd_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
log "github.com/cloudposse/atmos/pkg/logger"
"github.com/cloudposse/atmos/pkg/perf"
"github.com/cloudposse/atmos/pkg/reexec"
stepPkg "github.com/cloudposse/atmos/pkg/runner/step"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/ui"
u "github.com/cloudposse/atmos/pkg/utils"
Expand Down Expand Up @@ -249,7 +250,8 @@ func preCustomCommand(
if len(args) < requiredNoDefaultCount {
sb.WriteString(
fmt.Sprintf("Command requires at least %d argument(s) (no defaults provided for them):\n",
requiredNoDefaultCount))
requiredNoDefaultCount),
)

// List out which arguments are missing
missingIndex := 1
Expand Down Expand Up @@ -686,6 +688,9 @@ func executeCustomCommand(
log.Debug("Using working directory for custom command", "command", commandConfig.Name, "working_directory", workDir)
}

// Initialize step executor once before loop - reused across steps to preserve outputs.
executor := stepPkg.NewStepExecutor()

// Execute custom command's steps
for i, step := range commandConfig.Steps {
// Prepare template data for arguments
Expand Down Expand Up @@ -804,11 +809,48 @@ func executeCustomCommand(
commandToRun, err := e.ProcessTmpl(&atmosConfig, fmt.Sprintf("step-%d", i), step.Command, data, false)
errUtils.CheckErrorPrintAndExit(err, "", "")

// Execute the command step
commandName := fmt.Sprintf("%s-step-%d", commandConfig.Name, i)
// Determine step type - default to shell if not specified.
stepType := strings.TrimSpace(step.Type)
if stepType == "" {
stepType = "shell"
}

// Execute the step based on type.
switch stepType {
case "shell":
// Execute shell command (backward compatible).
commandName := fmt.Sprintf("%s-step-%d", commandConfig.Name, i)
err = e.ExecuteShell(commandToRun, commandName, workDir, env, false)
case "atmos":
// Execute atmos command.
args := strings.Fields(commandToRun)
err = e.ExecuteShellCommand(atmosConfig, "atmos", args, workDir, env, false, "")
default:
// Check if this is an extended step type (input, confirm, choose, etc.).
if stepPkg.IsExtendedStepType(stepType) {
// Convert Task to WorkflowStep for handler compatibility.
workflowStep := step.ToWorkflowStep()
// Update command with template-resolved value.
workflowStep.Command = commandToRun
// Propagate working directory to extended step if not already set.
if workflowStep.WorkingDirectory == "" {
workflowStep.WorkingDirectory = workDir
}

// Update environment variables for this step (reuse executor to preserve step outputs).
for _, envVar := range env {
parts := strings.SplitN(envVar, "=", 2)
if len(parts) == 2 {
executor.SetEnv(parts[0], parts[1])
}
}

// Pass the prepared environment with custom variables to the subprocess
err = e.ExecuteShell(commandToRun, commandName, workDir, env, false)
// Execute the extended step.
_, err = executor.Execute(context.Background(), &workflowStep)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
err = fmt.Errorf("%w: unsupported step type %q for custom command step %d", errUtils.ErrInvalidWorkflowStepType, stepType, i)
}
}
errUtils.CheckErrorPrintAndExit(err, "", "")
}
}
Expand Down Expand Up @@ -1128,7 +1170,8 @@ func resolveComponentPath(info *schema.ConfigAndStacksInfo, commandName string)
return handlePathResolutionError(err)
}

log.Debug("Resolved component from path",
log.Debug(
"Resolved component from path",
"original_path", info.ComponentFromArg,
"resolved_component", resolvedComponent,
"stack", info.Stack,
Expand Down Expand Up @@ -1272,7 +1315,8 @@ func StackFlagCompletion(cmd *cobra.Command, args []string, toComplete string) (
)
if err != nil {
// If resolution fails, fall through to list all stacks (graceful degradation)
log.Trace("Could not resolve path for stack completion, listing all stacks",
log.Trace(
"Could not resolve path for stack completion, listing all stacks",
"path", component,
"error", err,
)
Expand All @@ -1283,7 +1327,8 @@ func StackFlagCompletion(cmd *cobra.Command, args []string, toComplete string) (
return output, cobra.ShellCompDirectiveNoFileComp
}
component = resolvedComponent
log.Trace("Resolved path for stack completion",
log.Trace(
"Resolved path for stack completion",
"original", args[0],
"resolved", component,
)
Expand Down
25 changes: 16 additions & 9 deletions docs/prd/command-registry-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,8 @@ func Execute() error {

### Custom Command Override Behavior

> **Shipped behavior (corrected).** An earlier draft of this section claimed a same-named custom command "reuses the built-in and **replaces its behavior**." That is **not** what ships. Since **PR #2191** ("allow custom commands to merge into built-in namespaces at any level"), a custom command whose name collides with a built-in may **add subcommands** under that built-in's namespace, but it **cannot replace the built-in's behavior with steps** — the built-in is preserved and the steps are ignored with a warning. Replacing a built-in's behavior is an opt-in capability specified in [Overriding Built-in Commands with Step-Based Custom Commands](./custom-command-builtin-override.md) (via `override: true` plus an `invoke: built-in` step), not the default. The scenarios below are reframed to match reality.

**Scenario 1: Custom command with same name as built-in (top-level command)**

```yaml
Expand All @@ -636,7 +638,7 @@ commands:
- terraform {{ .arguments.subcommand }}
```

**Result:** Custom `terraform` command **reuses** the built-in `terraform` command and **replaces its behavior**.
**Result (default):** The built-in `terraform` command is **preserved** and the custom `steps` are **ignored** with the warning *"Custom command … defines steps that conflict with built-in command …; built-in behavior preserved, custom steps ignored"* (`cmd/cmd_utils.go`). To actually replace the built-in's behavior — and still reach the native `terraform` from a step — opt in with `override: true` and an `invoke: built-in` step, per the [override PRD](./custom-command-builtin-override.md).

**Scenario 2: Custom command extending built-in with subcommands**

Expand Down Expand Up @@ -706,12 +708,15 @@ func processCustomCommands(
}
```

**Key behaviors:**
- **Top-level custom command with existing name** → reuses registry command, can replace behavior or add subcommands
> **Note:** the snippet above is the original illustrative sketch and predates **PR #2191**. The current `processCustomCommands` (`cmd/cmd_utils.go`) uses `findSubcommand` at every level and, on a name collision, **reuses the existing built-in and ignores any custom `steps`** (warning emitted). It does not replace the built-in's behavior. See the corrected key behaviors below.

**Key behaviors (shipped):**
- **Top-level custom command with existing built-in name** → reuses the built-in; may add subcommands under it; custom `steps` are **ignored** (warning). Replacing behavior requires opt-in `override: true` — see the [override PRD](./custom-command-builtin-override.md).
- **Top-level custom command with new name** → creates new command
- **Nested custom commands** → always added as subcommands to parent
- **Nested custom commands, new name** → added as a new subcommand under the parent
- **Nested custom commands, name collision** → `findSubcommand` runs at every level, so a nested name that collides with an existing subcommand (built-in or one already added) **reuses the existing command**; if the colliding custom command declares `steps`, they are **ignored** (warning emitted). This is the same collision behavior as top-level commands — it is not limited to the top level.

**No changes needed to custom command processing** - the registry pattern coexists perfectly.
The registry pattern itself needs no changes for this; the override capability is layered on top via the [override PRD](./custom-command-builtin-override.md).

## Migration Guide

Expand Down Expand Up @@ -1817,19 +1822,21 @@ func RegisterPlugin(plugin *Plugin) {

### Q: Can custom commands still override built-in commands?

**A:** Yes. The execution order is:
**A:** Not by default. The execution order is:
1. Built-in commands registered via registry
2. Custom commands processed from atmos.yaml
3. If custom command has same name → overrides built-in
4. If custom command has new name → extends available commands
3. If a custom command has the **same name** as a built-in → it may **add subcommands** under that namespace, but its `steps` are **ignored** and the built-in's behavior is preserved (warning emitted) — see PR #2191.
4. If a custom command has a **new name** → extends available commands

Replacing a built-in command's behavior with steps is an **opt-in** capability (`override: true`), and a step can call the native built-in via `invoke: built-in`. See [Overriding Built-in Commands with Step-Based Custom Commands](./custom-command-builtin-override.md).

### Q: Do I need to update my atmos.yaml?

**A:** No. The registry pattern is purely internal. User-facing configuration and behavior remain unchanged.

### Q: What happens if I have a custom command named "about"?

**A:** Your custom command will override the built-in `about` command, just like it does today. The registry pattern doesn't change this behavior.
**A:** By default the built-in `about` command is preserved; a same-named custom command's `steps` are ignored (warning emitted), though you may add subcommands under it. To replace the built-in's behavior, opt in with `override: true` (see the [override PRD](./custom-command-builtin-override.md)). The registry pattern itself doesn't change this.

### Q: Can multiple commands register with the same name?

Expand Down
Loading
Loading