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

View file

@ -2,9 +2,9 @@
## Direction
The project is evolving from a focused AppImage manager into `upm`, a modular universal package manager. The target system manages multiple package sources through a shared headless core, keeps the CLI thin, and leaves room for future GUI frontends.
The project is evolving from a focused AppImage manager into `upm`, a modular universal package manager. The target system manages multiple package-manager modules through a shared headless application core, keeps frontend crates thin, and leaves room for future GUI frontends.
The initial rename-and-extraction slice has now landed as a hard cutover: the runtime surface is `upm`, the shared core is `upm-core`, and AppImage support is composed through `upm-appimage` instead of being embedded directly into the core.
The initial rename-and-extraction slice has now landed as a hard cutover: the runtime surface is `upm`, the shared core is `upm-core`, and AppImage support is beginning its transition into `upm-appimage` instead of remaining embedded directly into the core.
The near-term goal is a Linux-first platform with honest cross-platform architecture. Phase 2 will implement Linux package sources while establishing the abstractions needed for later macOS support and possible Windows support.
@ -14,7 +14,8 @@ The near-term goal is a Linux-first platform with honest cross-platform architec
- `aim-core` stops being the all-in-one backend and is split.
- AppImage support becomes an installable module named `upm-appimage`.
- Shared orchestration, config, state, resolution, ranking, and frontend-facing APIs move into `upm-core`.
- The `upm` crate becomes a thin CLI client over `upm-core`.
- The `upm` crate becomes a thin CLI frontend over `upm-core`.
- A future `upm-ui` crate will become a GUI frontend over the same `upm-core` application boundary.
- The rename is a hard cutover. Legacy `AIM_*` runtime interfaces are removed rather than preserved.
- The declarative package file starts in a hybrid mode and is intended to become the source of truth over time.
- Every `upm` invocation should detect drift between declared state and observed system state, then auto-sync metadata as needed.
@ -29,9 +30,10 @@ The near-term goal is a Linux-first platform with honest cross-platform architec
The intended workspace shape after the initial refactor is:
- `upm`: thin CLI frontend, ratatui config UI, command routing, presentation.
- `upm-core`: headless application layer, provider registry, resolution pipeline, state model, declarative sync engine, ranking, policies, and frontend-agnostic APIs.
- `upm-appimage`: AppImage provider module extracted from the current `aim-core` implementation.
- future provider modules: `upm-pacman`, `upm-aur`, `upm-flatpak`, `upm-cargo`, `upm-npm`, and later macOS or Windows-specific modules.
- `upm-ui`: future GUI frontend over the same application core.
- `upm-core`: headless application layer, public application facade, internal orchestration services, module registry, state model, declarative sync engine, ranking, policies, and frontend-agnostic APIs.
- `upm-appimage`: AppImage package-manager module extracted from the current `aim-core` implementation.
- future module crates: `upm-pacman`, `upm-aur`, `upm-flatpak`, `upm-cargo`, `upm-npm`, and later macOS or Windows-specific modules.
### Module Model
@ -42,7 +44,9 @@ UPM should stay modular in both code and packaging:
- distro packaging can offer grouped installs such as `upm-full`
- lighter installs can ship only the core and selected modules
This means provider capabilities, discovery, search, install, remove, inspect, and sync behavior need stable interfaces in `upm-core` rather than provider-specific branching in the CLI.
This means module capabilities, discovery, search, install, remove, inspect, and sync behavior need stable interfaces in `upm-core` rather than package-manager-specific branching in the CLI or GUI.
The public API shape should be one application facade in `upm-core`, backed by smaller internal services. Frontends should call the facade; they should not compose lower-level services or talk directly to modules.
### State Model
@ -65,7 +69,7 @@ Goals:
- create `upm-core` by extracting reusable infrastructure from `aim-core`
- reduce the CLI crate to a frontend over headless APIs
- isolate current AppImage-specific logic into `upm-appimage`
- compose provider behavior in the CLI through `ProviderRegistry` rather than hardcoded AppImage paths in `upm-core`
- remove direct AppImage composition from the CLI and move module composition into `upm-core`
- preserve current AppImage functionality and tests during the move
Exit criteria:
@ -76,18 +80,20 @@ Exit criteria:
### Milestone 1: AppImage On The New Core
Make the current AppImage implementation the first real module on the modular architecture.
Make the current AppImage implementation the first real package-manager module on the modular architecture.
Goals:
- validate the provider module contract using AppImage as the reference implementation
- move search, add, install, update, show, and remove behaviors behind core provider APIs
- prove the CLI can treat AppImage as just one enabled source
- establish `upm-core` as the sole application boundary for CLI and future GUI frontends
- validate the module contract using AppImage as the reference implementation
- move AppImage-specific acquisition backends behind `upm-appimage`
- prove the CLI can treat AppImage as just one enabled module
Exit criteria:
- AppImage support is no longer special-cased as the whole product
- provider registration and capability discovery exist in `upm-core`
- CLI command paths run through the `upm-core` application facade
- AppImage acquisition minutia no longer lives as top-level application concepts in `upm-core`
### Milestone 2: Linux Native Sources
@ -175,8 +181,8 @@ Exit criteria:
The following are explicitly not required to complete Phase 2:
- full macOS provider implementation
- Windows provider implementation
- full macOS module implementation
- Windows module implementation
- GUI frontend delivery
- forcing strict config-authoritative reconciliation before provider behavior is stable
- shipping every conceivable Linux package manager in the first expansion
@ -199,7 +205,7 @@ That means:
Implementation plans should follow this order:
1. rename and crate extraction
2. provider API definition and AppImage migration onto `upm-core`
2. application facade definition and AppImage migration behind a real module boundary
3. Linux provider onboarding in a stable order, likely `pacman` then `Flatpak`, then `AUR`, then `cargo`, then `npm`
4. ratatui configuration and ranking UX
5. declarative state model, drift detection, and `update` sync behavior