Skip to content

Commit 35ad9cd

Browse files
authored
feat: include discovery commands on CLI (#3119)
This PR overhauls the SuperPlane CLI command structure to make resource discovery and canvas workflows easier, while also making command implementation more maintainable. Before: ``` superplane <action> <resource> ``` Now ``` superplane <resource> <action> ``` The new structure makes it easier to discover things, build an internal structure to extend the commands and sub-commands going forward, and is also a pattern used in other CLIs - [see gh](https://github.com/cli/cli). ### CLI internals - Refactored CLI internals into a reusable command framework: - Added `pkg/cli/core` with shared command binding/context logic. - Added centralized output rendering with support for `text`, `json`, and `yaml`. - Reorganized CLI commands into resource-focused groups under `pkg/cli/commands/*` ### New discovery-focused commands: - `superplane integrations list --connected` - `superplane integrations list-resources --id <integration-id> --type <resource-type> [--parameters key=value,...]` - `superplane components list --from <integration>` - `superplane triggers list --from <integration>` ### Global output flag You can control the output of the commands now with `--output` flag. Available options are: json / yaml / text. --------- Signed-off-by: Lucas Pinheiro <lucas@superplane.com>
1 parent 9d3cf1f commit 35ad9cd

File tree

32 files changed

+1589
-601
lines changed

32 files changed

+1589
-601
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
name: superplane-cli
3+
description: Use when working with the SuperPlane CLI to discover available integrations, components, and triggers, and to build or troubleshoot canvases that connect trigger->component flows. Covers list/get command usage, interpreting configuration schemas, wiring channels between nodes, and resolving integration binding issues such as "integration is required".
4+
---
5+
6+
# SuperPlane CLI Canvas Workflow
7+
8+
Use this workflow to build or debug canvases from the CLI.
9+
10+
## Discover what exists
11+
12+
Run these first:
13+
14+
```bash
15+
superplane integrations list
16+
superplane integrations list --connected
17+
superplane triggers list
18+
superplane components list
19+
```
20+
21+
Narrow to one integration:
22+
23+
```bash
24+
superplane triggers list --from github
25+
superplane components list --from github
26+
superplane components list --from semaphore
27+
```
28+
29+
Use `--connected` to list organization-connected integration instances (not just available providers).
30+
31+
Inspect required config fields and example payloads:
32+
33+
```bash
34+
superplane triggers get github.onPush
35+
superplane components get semaphore.runWorkflow
36+
superplane components get github.runWorkflow
37+
superplane components get approval
38+
```
39+
40+
List runtime options for `integration-resource` fields:
41+
42+
```bash
43+
superplane integrations list-resources --id <integration-id> --type <type> --parameters key1=value1,key2=value2
44+
```
45+
46+
Use `superplane integrations list --connected` first to find valid integration IDs.
47+
48+
## Build canvas incrementally
49+
50+
Create a blank canvas first:
51+
52+
```bash
53+
superplane canvases create <name>
54+
superplane canvases get <name>
55+
```
56+
57+
Edit a canvas file and update via:
58+
59+
```bash
60+
superplane canvases update --file <canvas-file.yaml>
61+
```
62+
63+
Use this resource header:
64+
65+
```yaml
66+
apiVersion: v1
67+
kind: Canvas
68+
metadata:
69+
id: <canvas-id>
70+
name: <canvas-name>
71+
spec:
72+
nodes: []
73+
edges: []
74+
```
75+
76+
## Canvas YAML structure
77+
78+
Use this as the canonical shape when editing a canvas file.
79+
80+
Top-level fields:
81+
82+
- `apiVersion`: always `v1`
83+
- `kind`: always `Canvas`
84+
- `metadata.id`: canvas UUID (required for update)
85+
- `metadata.name`: canvas name
86+
- `spec.nodes`: list of trigger/component nodes
87+
- `spec.edges`: list of directed graph connections
88+
89+
Node structure:
90+
91+
- Common fields: `id`, `name`, `type`, `configuration`, `position`, `paused`, `isCollapsed`
92+
- Keep node `name` values unique within a canvas. Duplicate names can produce warnings and make expressions/diagnostics ambiguous.
93+
- `type` must be `TYPE_TRIGGER` or `TYPE_COMPONENT`
94+
- Trigger nodes must include `trigger.name`
95+
- Component nodes must include `component.name`
96+
- Integration-backed nodes should include `integration.id` (`integration.name` can be empty string)
97+
- `errorMessage` and `warningMessage` are optional but useful for troubleshooting
98+
- `metadata` is optional and usually server-populated
99+
100+
Edge structure:
101+
102+
- `sourceId`: upstream node id
103+
- `targetId`: downstream node id
104+
- `channel`: output channel from source node (`default`, `passed`, `approved`, etc.)
105+
106+
Minimal example:
107+
108+
```yaml
109+
apiVersion: v1
110+
kind: Canvas
111+
metadata:
112+
id: <canvas-id>
113+
name: <canvas-name>
114+
spec:
115+
nodes:
116+
- id: trigger-main
117+
name: github.onPush
118+
type: TYPE_TRIGGER
119+
trigger:
120+
name: github.onPush
121+
integration:
122+
id: <github-integration-id>
123+
name: ""
124+
configuration:
125+
repository: owner/repo
126+
refs:
127+
- type: equals
128+
value: refs/heads/main
129+
position:
130+
x: 120
131+
y: 100
132+
paused: false
133+
isCollapsed: false
134+
135+
- id: component-ci
136+
name: semaphore.runWorkflow
137+
type: TYPE_COMPONENT
138+
component:
139+
name: semaphore.runWorkflow
140+
integration:
141+
id: <semaphore-integration-id>
142+
name: ""
143+
configuration:
144+
project: <project-id-or-name>
145+
pipelineFile: .semaphore/semaphore.yml
146+
ref: refs/heads/main
147+
position:
148+
x: 480
149+
y: 100
150+
paused: false
151+
isCollapsed: false
152+
153+
edges:
154+
- sourceId: trigger-main
155+
targetId: component-ci
156+
channel: default
157+
```
158+
159+
## Node and edge wiring rules
160+
161+
Use `TYPE_TRIGGER` for trigger nodes and `TYPE_COMPONENT` for component nodes.
162+
163+
For triggers, set:
164+
165+
- `trigger.name` to the trigger id (example: `github.onPush`)
166+
167+
For components, set:
168+
169+
- `component.name` to the component id (example: `semaphore.runWorkflow`)
170+
171+
For graph flow, set edges:
172+
173+
- `sourceId` and `targetId` for connection
174+
- `channel` when routing specific outputs (example: `passed`, `approved`)
175+
176+
Typical gated flow:
177+
178+
1. Trigger -> CI component
179+
2. CI `passed` -> `approval`
180+
3. `approval` `approved` -> deploy component
181+
182+
## Configure integration-backed fields correctly
183+
184+
When a field type is `integration-resource` (such as `repository` or `project`), the org must have a configured integration instance for that provider.
185+
186+
Symptoms of missing binding:
187+
188+
- Node `errorMessage` contains `integration is required`
189+
190+
How to resolve:
191+
192+
1. Run `superplane integrations list --connected` and confirm required providers are connected for the org.
193+
2. Ensure the provider integration (GitHub, Semaphore, etc.) is installed and authenticated for the organization.
194+
3. Reopen the node config and select valid provider resources for required fields.
195+
4. Use `superplane integrations list-resources --id <integration-id> --type <type> --parameters ...` to inspect valid option IDs/names.
196+
5. Re-run `superplane canvases get <name>` and confirm node errors are cleared.
197+
198+
## Troubleshooting checklist
199+
200+
Run this after every update:
201+
202+
```bash
203+
superplane canvases get <name>
204+
```
205+
206+
Check:
207+
208+
- All required `configuration` fields are present.
209+
- Edges use the correct output channels.
210+
- No node `errorMessage` remains.
211+
- No node `warningMessage` indicates duplicate names (for example: `Multiple components named "semaphore.runWorkflow"`).
212+
- Expressions reference existing node names.

pkg/cli/check.go

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package canvases
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/superplanehq/superplane/pkg/cli/commands/canvases/models"
8+
"github.com/superplanehq/superplane/pkg/cli/core"
9+
"github.com/superplanehq/superplane/pkg/openapi_client"
10+
)
11+
12+
type createCommand struct {
13+
file *string
14+
}
15+
16+
func (c *createCommand) Execute(ctx core.CommandContext) error {
17+
filePath := ""
18+
if c.file != nil {
19+
filePath = *c.file
20+
}
21+
22+
if filePath != "" {
23+
if len(ctx.Args) > 0 {
24+
return fmt.Errorf("cannot use <canvas-name> together with --file")
25+
}
26+
return c.createFromFile(ctx, filePath)
27+
}
28+
29+
if len(ctx.Args) != 1 {
30+
return fmt.Errorf("either --file or <canvas-name> is required")
31+
}
32+
33+
name := ctx.Args[0]
34+
resource := models.Canvas{
35+
APIVersion: core.APIVersion,
36+
Kind: models.CanvasKind,
37+
Metadata: &openapi_client.CanvasesCanvasMetadata{Name: &name},
38+
Spec: models.EmptyCanvasSpec(),
39+
}
40+
41+
canvas := models.CanvasFromCanvas(resource)
42+
request := openapi_client.CanvasesCreateCanvasRequest{}
43+
request.SetCanvas(canvas)
44+
45+
_, _, err := ctx.API.CanvasAPI.CanvasesCreateCanvas(ctx.Context).Body(request).Execute()
46+
return err
47+
}
48+
49+
func (c *createCommand) createFromFile(ctx core.CommandContext, path string) error {
50+
// #nosec
51+
data, err := os.ReadFile(path)
52+
if err != nil {
53+
return fmt.Errorf("failed to read resource file: %w", err)
54+
}
55+
56+
_, kind, err := core.ParseYamlResourceHeaders(data)
57+
if err != nil {
58+
return err
59+
}
60+
61+
switch kind {
62+
case models.CanvasKind:
63+
resource, err := models.ParseCanvas(data)
64+
if err != nil {
65+
return err
66+
}
67+
68+
canvas := models.CanvasFromCanvas(*resource)
69+
request := openapi_client.CanvasesCreateCanvasRequest{}
70+
request.SetCanvas(canvas)
71+
72+
_, _, err = ctx.API.CanvasAPI.CanvasesCreateCanvas(ctx.Context).Body(request).Execute()
73+
return err
74+
default:
75+
return fmt.Errorf("unsupported resource kind %q", kind)
76+
}
77+
}

0 commit comments

Comments
 (0)