refactor: add upm application facade and module api

This commit is contained in:
stoorps 2026-03-21 23:43:14 +00:00
parent 005d6ebfdb
commit e2a01d3095
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
36 changed files with 1058 additions and 607 deletions

View file

@ -2,27 +2,92 @@
## Workspace Shape
`upm` is a Rust workspace with three main crates:
`upm` is a Rust workspace with three main crates today and a fourth planned frontend:
- `crates/upm-core`: source normalization, add/update orchestration, registry persistence, install policies, desktop integration, and the provider-composition seam.
- `crates/upm`: argument parsing, config loading, terminal UX, prompting, progress reporting, summary rendering, and provider assembly.
- `crates/upm-appimage`: AppImageHub transport, search-provider behavior, and exact add-resolution for AppImage-backed installs.
- `crates/upm-core`: the application layer for `upm`. It owns command orchestration, module contracts, module registry and composition, registry persistence, install policies, desktop integration, and the unified frontend-facing API that both the CLI and a future GUI will call.
- `crates/upm`: the CLI frontend over `upm-core`. It handles argument parsing, config loading, terminal UX, prompting, progress reporting, summary rendering, and config-driven module presentation.
- `crates/upm-appimage`: the AppImage package-manager module. It should own AppImage-specific acquisition backends, artifact selection, and install-resolution behavior.
- `crates/upm-ui` (planned): a GUI frontend over `upm-core`, not a second application layer.
The split keeps frontend-agnostic logic in `upm-core`, while concrete package-source behavior is composed at the CLI boundary. That keeps the headless layer reusable for future frontends without making provider behavior a permanent core dependency.
The intended split is strict:
- `upm-core` is effectively the application
- `upm` is one frontend over that application
- `upm-ui` will be another frontend over that application
- package-manager modules own their own implementation detail and speak to `upm-core` through normalized traits
That keeps frontend-agnostic logic in `upm-core`, makes a future GUI a first-class consumer instead of a later retrofit, and prevents frontend layers from accumulating package-manager-specific behavior.
## Application Boundary
The architectural boundary is:
- `upm` may know which modules exist for configuration, enablement, disablement, priority, and display
- `upm-ui` should operate under the same rule as the CLI: it talks to `upm-core`, not directly to modules
- `upm` must not talk directly to a package-manager module or implement module-specific logic
- `upm-core` owns the unified application interface used by the CLI now and a GUI later
- `upm-core` owns module registration, composition, enablement checks, and request fan-out
- `upm-core` fans requests out to enabled modules and aggregates normalized results
- each module owns its own internal backends, source quirks, artifact selection, and provider-specific rules
In practical terms, `upm-core` is where the product behavior lives. The CLI should remain replaceable.
## Public API Shape
`upm-core` should expose one high-level application facade to frontend crates.
- the public boundary should be an application-facing type such as `UpmApp`
- the facade should present operations like search, add, show, update, remove, and config management in product terms
- frontends should not compose lower-level orchestration services themselves
That public facade should stay thin. The internal implementation in `upm-core` can and should be split into smaller services such as:
- module registry and module loading
- search orchestration
- add planning and execution
- show resolution
- update planning and execution
- configuration and state services
This gives both frontends one stable application boundary without turning the facade into a god object. The orchestration depth stays inside `upm-core`, where it belongs.
## Module Tree
The intended tree is:
- `upm-core`
- public application facade
- internal orchestration services
- module registry and composition
- normalized contracts for package-manager modules
- frontend crates
- `upm` for CLI concerns only
- `upm-ui` for GUI concerns only
- module crates
- `upm-appimage`
- AppImageHub backend
- GitHub-backed AppImage acquisition
- GitLab-backed AppImage acquisition
- SourceForge-backed AppImage acquisition
- direct AppImage URL handling
- AppImage-specific artifact and metadata rules
The important constraint is that the top layer understands package-manager modules, not the inner mechanics of how each module finds or resolves artifacts.
## Core Flow
The main execution path is:
1. Parse CLI input and load runtime config in `upm`.
2. Assemble a `ProviderRegistry` in `crates/upm/src/providers.rs`.
3. Resolve the query into a normalized source in `upm-core`.
4. Build an add or update plan through core orchestration plus any registered external providers.
4. Download the selected AppImage into a staged path.
5. Verify integrity metadata when available.
6. Commit the payload into the managed install location.
7. Write desktop integration artifacts and refresh helper caches.
8. Persist registry state atomically.
2. Call the unified application facade in `upm-core`.
3. Let `upm-core` route the request into internal orchestration services.
4. Let those services select enabled modules and fan the request out through normalized module traits.
5. Aggregate normalized results into an add, show, update, search, or remove flow.
6. Download the selected AppImage into a staged path when the chosen module requires it.
7. Verify integrity metadata when available.
8. Commit the payload into the managed install location.
9. Write desktop integration artifacts and refresh helper caches.
10. Persist registry state atomically.
## Source And Provider Model
@ -35,7 +100,9 @@ Supported source classes currently include:
- direct URLs
- local file imports
Core source normalization and orchestration live in `crates/upm-core`. AppImageHub-specific transport and provider behavior live in `crates/upm-appimage` and are injected through `ProviderRegistry` rather than hardcoded into core entrypoints.
Core orchestration and normalized module contracts live in `crates/upm-core`. Package-manager-specific behavior belongs in module crates.
For the AppImage module, that means `crates/upm-appimage` is the package-manager boundary and should grow to own AppImage-specific backing sources internally. `upm-core` should coordinate the module through normalized traits, not absorb AppImageHub, GitHub-backed AppImage discovery, GitLab-backed AppImage discovery, SourceForge-backed AppImage discovery, or direct AppImage URL handling as first-class application concepts.
## Runtime Interface