Skip to content

[subtask] [Subtask 2/5] CLI Integration: Add --profile and --manifest flags #540

@github-actions

Description

@github-actions

Parent Issue: #307

Objective

Add CLI flags and configuration to enable headless deployment mode and pass the manifest path to the runtime.

Context

This builds on the foundation types from Subtask 1/5. It exposes headless mode to users through command-line arguments and wires the configuration through to the LifecycleManager initialization.

Implementation Details

1. Add CLI Flags to Serve Command

File: src/commands.rs

Add new fields to the Serve struct (around line 64):

/// Deployment profile: interactive (default) or headless
#[arg(long, value_enum)]
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option(DeploymentProfile),

/// Path to provisioning manifest (YAML or TOML). Required when --profile=headless
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub manifest: Option(PathBuf),

Import the type at the top:

use wassette::DeploymentProfile;

2. Add Validation Logic

File: src/serve.rs (or wherever the serve command is handled)

Add validation when processing the Serve command:

// Validate headless mode requirements
if let Some(DeploymentProfile::Headless) = serve_args.profile {
    if serve_args.manifest.is_none() {
        anyhow::bail!(
            "Headless deployment profile requires --manifest flag with path to provisioning manifest"
        );
    }
}

// Validate manifest exists if provided
if let Some(ref manifest_path) = serve_args.manifest {
    if !manifest_path.exists() {
        anyhow::bail!(
            "Manifest file not found: {}",
            manifest_path.display()
        );
    }
}

3. Wire Profile to LifecycleManager

File: Look for where LifecycleBuilder is constructed (likely in src/main.rs or src/serve.rs)

Update the builder chain:

let mut builder = LifecycleBuilder::new(component_dir);

// Set profile if provided, otherwise default to Interactive
if let Some(profile) = serve_args.profile {
    builder = builder.with_profile(profile);
}

// Additional existing builder calls...
let lifecycle_manager = builder.build().await?;

4. Store Manifest Path in Configuration

File: crates/wassette/src/config.rs

Add manifest path to LifecycleConfig:

manifest_path: Option(PathBuf),

Add accessor:

pub fn manifest_path(&self) -> Option<&Path> {
    self.manifest_path.as_deref()
}

Update LifecycleBuilder:

manifest_path: Option(PathBuf),

Add builder method:

pub fn with_manifest(mut self, manifest_path: PathBuf) -> Self {
    self.manifest_path = Some(manifest_path);
    self
}

Update initialization and build() to propagate the field.

5. Update CLI Documentation

File: docs/reference/cli.md (if it exists, otherwise note for future docs update)

Document new flags:

### `wassette serve --profile (PROFILE)`

Set the deployment profile:
- `interactive` (default): Permits runtime component loading and permission grants
- `headless`: Declarative-only mode for unattended deployments

### `wassette serve --manifest (PATH)`

Path to provisioning manifest (YAML or TOML). Required when using `--profile headless`.

Example:
```bash
wassette serve --profile headless --manifest ./deployment.yaml

## Acceptance Criteria
- [ ] `--profile` flag accepts "interactive" or "headless" values
- [ ] `--manifest` flag accepts a file path
- [ ] Validation error occurs if `--profile headless` is used without `--manifest`
- [ ] Validation error occurs if manifest path doesn't exist
- [ ] Profile is correctly passed to `LifecycleBuilder`
- [ ] Manifest path is stored in `LifecycleConfig`
- [ ] `cargo build` succeeds
- [ ] `cargo clippy` passes with no warnings
- [ ] Help text (`wassette serve --help`) shows new flags clearly

## Testing Strategy

### Manual Testing
```bash
# Should work: default interactive mode
cargo run -- serve --stdio

# Should work: explicit interactive mode
cargo run -- serve --stdio --profile interactive

# Should fail: headless without manifest
cargo run -- serve --stdio --profile headless

# Should fail: manifest doesn't exist
cargo run -- serve --stdio --profile headless --manifest /nonexistent/file.yaml

# Should work: headless with manifest (after creating example manifest)
echo "version: 1
components: []" > /tmp/test-manifest.yaml
cargo run -- serve --stdio --profile headless --manifest /tmp/test-manifest.yaml

Unit Tests

Add to src/commands.rs or appropriate test module:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_deployment_profile_cli_parsing() {
        // Test that clap can parse the enum values
        let args = Serve::try_parse_from(&[
            "serve",
            "--profile", "headless",
            "--manifest", "/tmp/manifest.yaml"
        ]);
        assert!(args.is_ok());
    }
}

Dependencies

  • Subtask 1/5: Requires DeploymentProfile enum

References

AI generated by Plan for #307

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions