feat: implement `extension rm` command
Opened by swampadmin · 12/20/2024
Summary
Add swamp extension rm <name> (aliased as extension remove) to cleanly remove a pulled extension and all its installed files.
Why this is the right approach
The files array in upstream_extensions.json is the key.
When you pull an extension, its files are scattered across multiple directories — models go to extensions/models/, workflows to extensions/workflows/, bundles to .swamp/bundles/, and additional files go to the models dir too. There's no single directory per extension. The only reliable way to know "which files belong to @keeb/ssh" is the file manifest added in PR #520. Without it, removal would require guessing based on directory names or forcing the user to specify files manually.
Warn on reverse dependencies instead of blocking.
If @keeb/ssh depends on @keeb/base and you remove @keeb/base, @keeb/ssh may break. But blocking removal is too rigid — the user might be about to remove both, or might know their setup doesn't need the dependency. A warning gives them the information to make the right call. This matches how npm uninstall works.
Error on legacy entries (no files array) with a re-pull hint.
Best-effort scanning would be fragile — we'd have to guess which files belong to an extension by pattern-matching directory names, and we'd almost certainly miss workflows, bundles, or additional files. The safe answer is: "we don't know what files this extension installed, re-pull with --force to get the manifest, then rm." One-time operation for anyone who pulled before PR #520.
Prune empty directories after deletion.
Extensions typically install into a subdirectory like extensions/models/ssh/. After deleting all files, leaving an empty ssh/ folder is confusing — it looks like something is still installed.
Co-locate JSON mutations in extension_pull.ts.
All mutations to upstream_extensions.json use the same lockfile + atomic-write pattern. Keeping removeUpstreamExtension next to updateUpstreamExtensions means one place to maintain the concurrency logic.
Proposed behavior
swamp extension rm @keeb/ssh- Validate the extension name format (
@namespace/name) - Read
upstream_extensions.jsonand find the entry - If no
filesarray exists (legacy entry), error with a re-pull hint - Check if other installed extensions list this one as a dependency — warn if so
- Show files that will be deleted and prompt for confirmation (unless
--force) - Delete each file listed in the
filesarray (skip gracefully if already missing) - Prune empty parent directories left behind
- Remove the entry from
upstream_extensions.jsonatomically - Render success output
Implementation plan
Files to create
| File | Purpose |
|---|---|
src/cli/commands/extension_rm.ts |
Command implementation (follows model_delete.ts pattern) |
src/presentation/output/extension_rm_output.ts |
Render functions for log + JSON output |
src/presentation/output/extension_rm_output_test.ts |
Output unit tests |
src/cli/commands/extension_rm_test.ts |
Command unit tests |
integration/extension_rm_test.ts |
Integration tests (pull then rm, verify cleanup) |
Files to modify
| File | Change |
|---|---|
src/cli/commands/extension.ts |
Register rm subcommand |
src/cli/commands/extension_pull.ts |
Export new removeUpstreamExtension function |
Command interface
swamp extension rm <extension>
--repo-dir <dir> Repository directory (default: ".")
-f, --force Skip confirmation prompt
Aliases: extension removeEdge cases
- File already deleted manually: Skip gracefully, count as
filesSkipped - Extension not in JSON:
UserError("Extension @ns/name is not installed.") - Legacy entry without
files: Error with re-pull hint - Concurrent pull/rm: Handled by existing lockfile mechanism
- Version in argument:
@keeb/ssh@1.0.0— ignore version, remove whatever is installed
Dependency warning
For each other entry in upstream_extensions.json, check if a manifest.yaml exists among its tracked files. If so, parse it and check its dependencies array. If the target extension appears, warn:
Warning: @keeb/other depends on @keeb/ssh. Removing it may break that extension.Output
Log mode:
Removed @keeb/ssh (v2026.02.26.1) — deleted 3 filesJSON mode:
{
"removed": {
"name": "@keeb/ssh",
"version": "2026.02.26.1",
"filesDeleted": 3,
"filesSkipped": 0,
"dirsRemoved": 1
}
}Prerequisite
- PR #520 (track extracted files in
upstream_extensions.json) must be merged first
Closed
No activity in this phase yet.
Sign in to post a ripple.