Skip to content

Commit 2a722ee

Browse files
committed
SIP 025 - spin deps cli dx
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
1 parent 9796cd8 commit 2a722ee

1 file changed

Lines changed: 246 additions & 0 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
title = "SIP 025 - spin deps cli dx"
2+
template = "main"
3+
date = "2026-04-09T00:00:00Z"
4+
5+
---
6+
7+
Summary: A CLI command (`spin deps add`) for adding component dependencies to a Spin application, with interactive prompts for selecting components, exports, and capability inheritance.
8+
9+
Owner(s): [brian.hardock@fermyon.com](mailto:brian.hardock@fermyon.com)
10+
11+
Created: April 9, 2026
12+
13+
# Background
14+
15+
[SIP 020](docs/content/sips/020-component-dependencies.md) introduced the concept of component dependencies in Spin, allowing developers to compose components together by declaring dependencies in `spin.toml`. However, authoring the dependency entries by hand requires understanding the TOML schema, knowing which exports a component offers, and correctly configuring capability inheritance — all of which are error-prone.
16+
17+
`spin deps add` provides a guided CLI experience for adding a component dependency. It resolves the source, inspects the Wasm component's exports, and writes the correct entry into `spin.toml`, along with regenerating the `spin-dependencies.wit` file.
18+
19+
# Proposal
20+
21+
## Command Syntax
22+
23+
```
24+
spin deps add <source> [options]
25+
```
26+
27+
### Source Formats
28+
29+
The `<source>` positional argument accepts three forms:
30+
31+
| Form | Example | Description |
32+
|------|---------|-------------|
33+
| Local path | `./my-component.wasm` | A path to a Wasm component on disk |
34+
| HTTP URL | `https://example.com/component.wasm` | A remote Wasm component (requires `--digest`) |
35+
| Registry reference | `aws:client@1.0.0` | A package from a component registry |
36+
37+
### Options
38+
39+
| Flag | Description |
40+
|------|-------------|
41+
| `--to <component-id>` | Target component to add the dependency to. Prompted if omitted and the app has multiple components. |
42+
| `-f, --from <path>` | Path to the `spin.toml` manifest. Defaults to the current directory. |
43+
| `--export <name>` | Export to use from the dependency. Prompted if omitted and the component has multiple exports. |
44+
| `-d, --digest <sha256>` | SHA-256 digest for verifying HTTP downloads. Required for HTTP sources. |
45+
| `-r, --registry <url>` | Override the default registry. Only applies to registry sources. |
46+
| `--inherit <value>` | Capability inheritance: `true`/`all`, `false`/`none`, or comma-separated capabilities. Prompted if omitted and the dependency requires capabilities. |
47+
48+
## Interactive Prompts
49+
50+
When optional flags are omitted, `spin deps add` presents interactive prompts to guide the developer through each decision. The following sections illustrate the prompt flow.
51+
52+
### Step 1: Select the target component
53+
54+
If `--to` is omitted and the application has more than one component, the user is prompted:
55+
56+
```
57+
$ spin deps add aws:client@1.0.0
58+
59+
? Which component should the dependency be added to?
60+
> api-server
61+
worker
62+
dashboard
63+
```
64+
65+
If the application has exactly one component, it is selected automatically.
66+
67+
### Step 2: Select the export
68+
69+
The command inspects the resolved Wasm component to enumerate its exports. If `--export` is omitted, the prompt flow depends on the number of packages and interfaces.
70+
71+
#### Single export — auto-selected
72+
73+
If the component exports only one interface, it is selected automatically with no prompt.
74+
75+
#### Multiple packages — select a package first
76+
77+
If the component exports interfaces from multiple packages, the user first selects a package:
78+
79+
```
80+
? Which package should be used?
81+
> aws:client@1.0.0
82+
aws:util@1.0.0
83+
```
84+
85+
#### Within-package selection — all or a specific interface
86+
87+
After a package is selected (or if there is only one), the user chooses between all exports from that package or a single specific interface:
88+
89+
```
90+
? Which export should be used?
91+
> All from aws:client@1.0.0
92+
aws:client/s3@1.0.0
93+
aws:client/dynamodb@1.0.0
94+
aws:client/sqs@1.0.0
95+
```
96+
97+
Selecting **"All from aws:client@1.0.0"** records `aws:client@1.0.0` as the dependency name (a package-level selector). Selecting a specific interface records that interface (e.g. `aws:client/s3@1.0.0`).
98+
99+
#### Explicit `--export` flag
100+
101+
The `--export` flag accepts the same forms:
102+
103+
- **Specific interface:** `--export aws:client/s3@1.0.0`
104+
- **Package selector:** `--export aws:client@1.0.0` (selects all matching exports)
105+
- **Plain name:** `--export my-export`
106+
107+
### Step 3: Select capability inheritance
108+
109+
The command inspects the dependency's imports and matches them against known capability sets (e.g. `allowed_outbound_hosts`, `ai_models`, `key_value_stores`) using semver-compatible matching. If the dependency requires any capabilities and `--inherit` is omitted, the user is prompted:
110+
111+
```
112+
This dependency requires the following capabilities: allowed_outbound_hosts, ai_models
113+
114+
? Select capabilities to inherit from the parent component
115+
> All
116+
allowed_outbound_hosts
117+
ai_models
118+
```
119+
120+
Selecting **"All"** sets `inherit_configuration = true` in the manifest. Selecting individual capabilities records them as a list (e.g. `inherit_configuration = ["allowed_outbound_hosts"]`). Selecting nothing results in no inheritance.
121+
122+
#### Explicit `--inherit` flag
123+
124+
- `--inherit true` or `--inherit all` → inherits all capabilities
125+
- `--inherit false` or `--inherit none` → inherits nothing
126+
- `--inherit allowed_outbound_hosts,ai_models` → inherits only those capabilities
127+
128+
### Step 4: Write to manifest and regenerate WIT
129+
130+
After all selections are made, the command:
131+
132+
1. Serializes the dependency into the `[component.<id>.dependencies]` table in `spin.toml`
133+
2. Regenerates `spin-dependencies.wit` in the component's build directory
134+
3. Prints a confirmation message
135+
136+
```
137+
Added aws:client@1.0.0 to component 'api-server'
138+
139+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts, ai_models
140+
You may need to add configuration for these capabilities to your component.
141+
```
142+
143+
## End-to-End Examples
144+
145+
### Fully interactive
146+
147+
```
148+
$ spin deps add aws:client@1.0.0
149+
150+
? Which component should the dependency be added to?
151+
> api-server
152+
153+
? Which package should be used?
154+
> aws:client@1.0.0
155+
156+
? Which export should be used?
157+
> aws:client/s3@1.0.0
158+
159+
This dependency requires the following capabilities: allowed_outbound_hosts
160+
161+
? Select capabilities to inherit from the parent component
162+
> allowed_outbound_hosts
163+
164+
Added aws:client/s3@1.0.0 to component 'api-server'
165+
166+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts
167+
You may need to add configuration for these capabilities to your component.
168+
```
169+
170+
### Fully non-interactive
171+
172+
```
173+
$ spin deps add aws:client@1.0.0 \
174+
--to api-server \
175+
--export aws:client/s3@1.0.0 \
176+
--inherit allowed_outbound_hosts
177+
178+
Added aws:client/s3@1.0.0 to component 'api-server'
179+
180+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts
181+
You may need to add configuration for these capabilities to your component.
182+
```
183+
184+
### Local component with all capabilities
185+
186+
```
187+
$ spin deps add ./my-component.wasm --to worker --export my-export --inherit all
188+
189+
Added my-export to component 'worker'
190+
```
191+
192+
### HTTP source
193+
194+
```
195+
$ spin deps add https://example.com/component.wasm \
196+
--digest abc123... \
197+
--to dashboard \
198+
--export foo:bar/baz@0.1.0 \
199+
--inherit false
200+
201+
Added foo:bar/baz@0.1.0 to component 'dashboard'
202+
```
203+
204+
## Resulting Manifest Entries
205+
206+
The command produces entries in `spin.toml` matching the schema defined in [SIP 020](docs/content/sips/020-component-dependencies.md):
207+
208+
```toml
209+
# Package-level selector with full inheritance
210+
[component.api-server.dependencies]
211+
"aws:client@1.0.0" = { version = "=1.0.0", package = "aws:client", inherit_configuration = true }
212+
213+
# Specific interface with selective inheritance
214+
[component.api-server.dependencies]
215+
"aws:client/s3@1.0.0" = { version = "=1.0.0", package = "aws:client", inherit_configuration = ["allowed_outbound_hosts"] }
216+
217+
# Local dependency with no inheritance
218+
[component.worker.dependencies]
219+
"my-export" = { path = "my-component.wasm" }
220+
221+
# HTTP dependency
222+
[component.dashboard.dependencies]
223+
"foo:bar/baz@0.1.0" = { url = "https://example.com/component.wasm", digest = "sha256:abc123..." }
224+
```
225+
226+
## Capability Detection
227+
228+
The command detects required capabilities by inspecting the dependency's component-level imports and matching them against known capability sets using **semver-compatible** matching (via `wac_graph::types::are_semver_compatible`). This means a dependency importing `wasi:http/outgoing-handler@0.2.7` correctly matches the `allowed_outbound_hosts` capability set even though the set is defined with `@0.2.6`.
229+
230+
The recognized capability sets are:
231+
232+
| Capability | Example interfaces |
233+
|---|---|
234+
| `ai_models` | `fermyon:spin/llm` |
235+
| `allowed_outbound_hosts` | `wasi:http/outgoing-handler`, `wasi:sockets/tcp`, `fermyon:spin/mqtt` |
236+
| `environment` | `wasi:cli/environment` |
237+
| `files` | `wasi:filesystem/preopens` |
238+
| `key_value_stores` | `fermyon:spin/key-value` |
239+
| `sqlite_databases` | `fermyon:spin/sqlite` |
240+
| `variables` | `fermyon:spin/variables` |
241+
242+
## Potential Future Work
243+
244+
### Multiple selections within a single package
245+
246+
The current design allows selecting either **all** exports from a package or a **single** specific interface. A natural extension would be to support selecting **multiple** (but not all) interfaces from the same package in a single invocation. For example, a multi-select prompt could allow the user to pick both `aws:client/s3@1.0.0` and `aws:client/dynamodb@1.0.0` without selecting the entire `aws:client@1.0.0` package. This would generate one dependency entry per selected interface and avoid requiring the user to run `spin deps add` multiple times for the same package.

0 commit comments

Comments
 (0)