Skip to content

feat(extensions): add extension deprecation support to CLI#1455

Merged
stack72 merged 3 commits into
mainfrom
worktree-425
May 27, 2026
Merged

feat(extensions): add extension deprecation support to CLI#1455
stack72 merged 3 commits into
mainfrom
worktree-425

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented May 27, 2026

Summary

  • Add swamp extension deprecate and swamp extension undeprecate CLI commands that mark/unmark an entire extension as deprecated in the registry
  • Surface deprecation notices in search (indicator), info (details), pull (warning), outdated (status), update (status), and list --check-updates (indicator)
  • Deprecated extensions remain pullable and resolvable — existing workflows don't break
  • Aligned with the existing swamp-club registry API (POST /deprecate with {reason, supersededBy?}, POST /undeprecate with no body)

Closes swamp-club#425

Test plan

  • deno check passes
  • deno lint clean
  • deno fmt clean
  • 10 new unit tests (deprecate + undeprecate generators and previews) all pass
  • 21 existing extension tests still pass (info, yank, update service)
  • deno run compile succeeds
  • Manual test: swamp extension deprecate @namespace/ext --reason "..." --superseded-by @other/ext
  • Manual test: swamp extension undeprecate @namespace/ext
  • File UAT issue in swamp-uat for end-to-end CLI coverage
  • File docs issue in swamp-club for manual reference/how-to updates

🤖 Generated with Claude Code

…b#425)

Add `swamp extension deprecate` and `swamp extension undeprecate`
commands, plus deprecation notices in search, info, pull, outdated,
update, and list output. Deprecated extensions remain pullable —
this is a soft lifecycle state that signals users to migrate without
breaking existing workflows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
github-actions[bot]

This comment was marked as outdated.

github-actions[bot]

This comment was marked as outdated.

github-actions[bot]

This comment was marked as outdated.

stack72 and others added 2 commits May 27, 2026 18:38
- Fix checkExtensionVersion to check version before deprecation so
  updates (including security patches) are never silently skipped for
  deprecated extensions. Deprecation only surfaces when already up-to-date.
- Add 4 missing tests: deprecated path in checkExtensionVersion (with
  successor, with null deprecatedAt fallthrough), deprecated+update
  returns update_available, buildUpdateResult counts deprecated correctly.
- Add pull deprecated_warning event test.
- Cache pre-fetched extension info in pull generator to avoid double
  registry API call.
- Fix column alignment in extension info renderer (Superseded: 13 chars).
- Add hasDeprecated field to outdated JSON output for script parity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Clean, well-structured PR that adds extension deprecation/undeprecation as a soft lifecycle state. The implementation closely mirrors the existing yank/unyank pattern, which makes the codebase consistent and easy to navigate.

Blocking Issues

None.

Suggestions

  1. Comment positioning in extension_outdated.ts:88-89: The explanatory comment about filtered statuses (up_to_date and updated) now sits directly after the deprecated case's closing brace rather than at the end of the switch. It still reads correctly, but the indentation could be misleading — a reader might think it's about the deprecated case specifically. Consider moving it above the switch or after its closing }.

  2. checkExtensionVersion precedence logic is well-reasoned: The decision to surface update_available over deprecated (so security patches aren't skipped) with the pull warning covering the deprecated+update case is a solid design choice. The comment in the domain service explaining this is helpful.

DDD Assessment

  • Proper layered architecture: CLI → libswamp generator → infrastructure deps
  • DeprecatedStatus correctly extends the ExtensionUpdateStatus discriminated union in the domain service
  • Dependency injection via ExtensionDeprecateDeps / ExtensionUndeprecateDeps interfaces follows the established pattern
  • Input validation lives in the preview (application layer), generator assumes valid input — consistent with yank/unyank
  • buildUpdateResult correctly counts deprecated extensions in total without incrementing upToDate, updated, or failed

Other Notes

  • All new files have AGPLv3 license headers ✓
  • libswamp import boundary respected — CLI commands and renderers import from mod.ts
  • Both log and json output modes supported in all renderers ✓
  • 14 new tests covering generators, previews, domain service, and pull integration ✓
  • API client methods inherit the existing 15s AbortSignal timeout from this.fetch
  • mod.ts barrel exports all new public types ✓

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI UX Review

Blocking

None.

Suggestions

  1. extension update log summary silently drops deprecated extensionssrc/presentation/renderers/extension_update.ts:225 prints e.g. 3 extension(s): 0 updated, 1 up to date, 0 failed with no mention of the 2 deprecated ones. The individual warn lines appear above, so the data isn't lost, but the summary line can mislead users who scan only that line. Consider appending , N deprecated when the count is nonzero (same pattern outdated uses with hasDeprecated).

  2. UpdateSummary math doesn't add up in JSON modesrc/domain/extensions/extension_update_service.ts: total counts deprecated extensions but none of upToDate / updated / failed do, so total > upToDate + updated + failed whenever any extension is deprecated. (The same gap exists today for not_found.) Scripts that validate summary math will be surprised. A deprecated field in UpdateSummary would close this, similar to how outdated explicitly exposes hasDeprecated.

  3. deprecatedByUserId leaks into extension info --jsonsrc/libswamp/extensions/info.ts: e.data is serialized wholesale in JSON mode, so deprecatedByUserId (an internal registry user ID) appears in the output even though it is never shown in log mode. Whether that exposure is intentional is worth a quick check; if it is, it should arguably appear in log mode too (similar to how yankedAt and yankReason are both shown).

Verdict

PASS — both new commands (deprecate / undeprecate) and all five updated surfaces (search, info, pull, outdated, list/update) handle log and JSON modes correctly. Flag names, confirmation prompts, and error messages are consistent with the existing yank/unyank pattern. Exit codes are correct (deprecated does not trigger exit 1). No blocking issues.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial Review

Critical / High

None found. The code follows established patterns (yank/unyank), error handling is consistent, and the logic is correct across all production paths.

Medium

  1. Dead code path: extension_list --check-updates will never show (deprecated)src/presentation/renderers/extension_list.ts:106 and the "deprecated" union member in EnrichedExtensionListEntry.updateStatus (line 45) are wired into the renderer, but the data pipeline that feeds them can never produce the value. Two structural barriers:

    • src/cli/commands/extension_list_freshness.ts:32ExtensionListFreshnessDeps.getLatestVersion returns string | null, discarding all deprecation fields. The wiring at extension_list.ts:149-150 fetches the full ExtensionInfo from the API client (which now includes deprecatedAt, etc.) but only returns info?.latestVersion ?? null.
    • extension_list_freshness.ts:154checkExtensionVersion is called without the 4th deprecation parameter, so it can never return "deprecated".
    • extension_list_freshness.ts:158-160 — Even if those two issues were fixed, the mapping only distinguishes "update_available" from everything else (mapping the rest to "up_to_date"), so "deprecated" would still be mapped to "up_to_date".

    Net effect: The design doc (lines 9-33 of the design/extension.md changes) claims swamp extension list --check-updates marks deprecated extensions with (deprecated), but this doesn't work. Not a regression (the renderer and type additions are correct), but the feature is incomplete. Consider a follow-up to wire getLatestVersion → a richer return type that includes deprecation fields, pass them to checkExtensionVersion, and propagate the status.

Low

  1. extension_outdated.ts renderer switch comment placementsrc/presentation/renderers/extension_outdated.ts:88-89: the comment // up_to_date and updated are filtered before reaching the renderer now sits outside the deprecated case block but still inside the switch body, which reads as if it explains why deprecated is the last case rather than why up_to_date/updated are absent. No functional issue — just potentially confusing for future readers.

Verdict

PASS — The core deprecation/undeprecation flow, update service integration, pull warnings, search/info/outdated surfacing, and exit-code semantics are all correctly implemented with good test coverage. The extension_list --check-updates gap is a missing data pipeline connection, not a correctness bug in the code that was written. The PR is safe to merge; the list enrichment can be addressed in a follow-up.

@stack72 stack72 merged commit b153e3d into main May 27, 2026
11 checks passed
@stack72 stack72 deleted the worktree-425 branch May 27, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant