Skip to content

Commit 7708392

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

1 file changed

Lines changed: 248 additions & 0 deletions

File tree

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
title = "SIP 024 - 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`. [SIP 023](docs/content/sips/023-granular-capability-inheritance.md) extended this with per-dependency, granular capability inheritance — replacing the all-or-nothing `dependencies_inherit_configuration` boolean with a flexible `inherit_configuration` field that accepts `true`, `false`, or a list of specific capability keys.
16+
17+
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.
18+
19+
`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.
20+
21+
# Proposal
22+
23+
## Command Syntax
24+
25+
```
26+
spin deps add <source> [options]
27+
```
28+
29+
### Source Formats
30+
31+
The `<source>` positional argument accepts three forms:
32+
33+
| Form | Example | Description |
34+
|------|---------|-------------|
35+
| Local path | `./my-component.wasm` | A path to a Wasm component on disk |
36+
| HTTP URL | `https://example.com/component.wasm` | A remote Wasm component (requires `--digest`) |
37+
| Registry reference | `aws:client@1.0.0` | A package from a component registry |
38+
39+
### Options
40+
41+
| Flag | Description |
42+
|------|-------------|
43+
| `--to <component-id>` | Target component to add the dependency to. Prompted if omitted and the app has multiple components. |
44+
| `-f, --from <path>` | Path to the `spin.toml` manifest. Defaults to the current directory. |
45+
| `--export <name>` | Export to use from the dependency. Prompted if omitted and the component has multiple exports. |
46+
| `-d, --digest <sha256>` | SHA-256 digest for verifying HTTP downloads. Required for HTTP sources. |
47+
| `-r, --registry <url>` | Override the default registry. Only applies to registry sources. |
48+
| `--inherit <value>` | Capability inheritance: `true`/`all`, `false`/`none`, or comma-separated capabilities. Prompted if omitted and the dependency requires capabilities. |
49+
50+
## Interactive Prompts
51+
52+
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.
53+
54+
### Step 1: Select the target component
55+
56+
If `--to` is omitted and the application has more than one component, the user is prompted:
57+
58+
```
59+
$ spin deps add aws:client@1.0.0
60+
61+
? Which component should the dependency be added to?
62+
> api-server
63+
worker
64+
dashboard
65+
```
66+
67+
If the application has exactly one component, it is selected automatically.
68+
69+
### Step 2: Select the export
70+
71+
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.
72+
73+
#### Single export — auto-selected
74+
75+
If the component exports only one interface, it is selected automatically with no prompt.
76+
77+
#### Multiple packages — select a package first
78+
79+
If the component exports interfaces from multiple packages, the user first selects a package:
80+
81+
```
82+
? Which package should be used?
83+
> aws:client@1.0.0
84+
aws:util@1.0.0
85+
```
86+
87+
#### Within-package selection — all or a specific interface
88+
89+
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:
90+
91+
```
92+
? Which export should be used?
93+
> All from aws:client@1.0.0
94+
aws:client/s3@1.0.0
95+
aws:client/dynamodb@1.0.0
96+
aws:client/sqs@1.0.0
97+
```
98+
99+
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`).
100+
101+
#### Explicit `--export` flag
102+
103+
The `--export` flag accepts the same forms:
104+
105+
- **Specific interface:** `--export aws:client/s3@1.0.0`
106+
- **Package selector:** `--export aws:client@1.0.0` (selects all matching exports)
107+
- **Plain name:** `--export my-export`
108+
109+
### Step 3: Select capability inheritance
110+
111+
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:
112+
113+
```
114+
This dependency requires the following capabilities: allowed_outbound_hosts, ai_models
115+
116+
? Select capabilities to inherit from the parent component
117+
> All
118+
allowed_outbound_hosts
119+
ai_models
120+
```
121+
122+
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.
123+
124+
#### Explicit `--inherit` flag
125+
126+
- `--inherit true` or `--inherit all` → inherits all capabilities
127+
- `--inherit false` or `--inherit none` → inherits nothing
128+
- `--inherit allowed_outbound_hosts,ai_models` → inherits only those capabilities
129+
130+
### Step 4: Write to manifest and regenerate WIT
131+
132+
After all selections are made, the command:
133+
134+
1. Serializes the dependency into the `[component.<id>.dependencies]` table in `spin.toml`
135+
2. Regenerates `spin-dependencies.wit` in the component's build directory
136+
3. Prints a confirmation message
137+
138+
```
139+
Added aws:client@1.0.0 to component 'api-server'
140+
141+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts, ai_models
142+
You may need to add configuration for these capabilities to your component.
143+
```
144+
145+
## End-to-End Examples
146+
147+
### Fully interactive
148+
149+
```
150+
$ spin deps add aws:client@1.0.0
151+
152+
? Which component should the dependency be added to?
153+
> api-server
154+
155+
? Which package should be used?
156+
> aws:client@1.0.0
157+
158+
? Which export should be used?
159+
> aws:client/s3@1.0.0
160+
161+
This dependency requires the following capabilities: allowed_outbound_hosts
162+
163+
? Select capabilities to inherit from the parent component
164+
> allowed_outbound_hosts
165+
166+
Added aws:client/s3@1.0.0 to component 'api-server'
167+
168+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts
169+
You may need to add configuration for these capabilities to your component.
170+
```
171+
172+
### Fully non-interactive
173+
174+
```
175+
$ spin deps add aws:client@1.0.0 \
176+
--to api-server \
177+
--export aws:client/s3@1.0.0 \
178+
--inherit allowed_outbound_hosts
179+
180+
Added aws:client/s3@1.0.0 to component 'api-server'
181+
182+
NOTE: This dependency requires the following capabilities: allowed_outbound_hosts
183+
You may need to add configuration for these capabilities to your component.
184+
```
185+
186+
### Local component with all capabilities
187+
188+
```
189+
$ spin deps add ./my-component.wasm --to worker --export my-export --inherit all
190+
191+
Added my-export to component 'worker'
192+
```
193+
194+
### HTTP source
195+
196+
```
197+
$ spin deps add https://example.com/component.wasm \
198+
--digest abc123... \
199+
--to dashboard \
200+
--export foo:bar/baz@0.1.0 \
201+
--inherit false
202+
203+
Added foo:bar/baz@0.1.0 to component 'dashboard'
204+
```
205+
206+
## Resulting Manifest Entries
207+
208+
The command produces entries in `spin.toml` matching the schema defined in [SIP 020](docs/content/sips/020-component-dependencies.md) and the per-dependency `inherit_configuration` field introduced in [SIP 023](docs/content/sips/023-granular-capability-inheritance.md):
209+
210+
```toml
211+
# Package-level selector with full inheritance
212+
[component.api-server.dependencies]
213+
"aws:client@1.0.0" = { version = "=1.0.0", package = "aws:client", inherit_configuration = true }
214+
215+
# Specific interface with selective inheritance
216+
[component.api-server.dependencies]
217+
"aws:client/s3@1.0.0" = { version = "=1.0.0", package = "aws:client", inherit_configuration = ["allowed_outbound_hosts"] }
218+
219+
# Local dependency with no inheritance
220+
[component.worker.dependencies]
221+
"my-export" = { path = "my-component.wasm" }
222+
223+
# HTTP dependency
224+
[component.dashboard.dependencies]
225+
"foo:bar/baz@0.1.0" = { url = "https://example.com/component.wasm", digest = "sha256:abc123..." }
226+
```
227+
228+
## Capability Detection
229+
230+
The command detects required capabilities by inspecting the dependency's component-level imports and matching them against the capability sets defined in [SIP 023](docs/content/sips/023-granular-capability-inheritance.md) 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`.
231+
232+
The recognized capability sets are:
233+
234+
| Capability | Example interfaces |
235+
|---|---|
236+
| `ai_models` | `fermyon:spin/llm` |
237+
| `allowed_outbound_hosts` | `wasi:http/outgoing-handler`, `wasi:sockets/tcp`, `fermyon:spin/mqtt` |
238+
| `environment` | `wasi:cli/environment` |
239+
| `files` | `wasi:filesystem/preopens` |
240+
| `key_value_stores` | `fermyon:spin/key-value` |
241+
| `sqlite_databases` | `fermyon:spin/sqlite` |
242+
| `variables` | `fermyon:spin/variables` |
243+
244+
## Potential Future Work
245+
246+
### Multiple selections within a single package
247+
248+
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)