|
| 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