Compare commits
No commits in common. "upm" and "main" have entirely different histories.
131 changed files with 961 additions and 3431 deletions
|
|
@ -2,92 +2,25 @@
|
||||||
|
|
||||||
## Workspace Shape
|
## Workspace Shape
|
||||||
|
|
||||||
`upm` is a Rust workspace with three main crates today and a fourth planned frontend:
|
`aim` is a Rust workspace with two main crates:
|
||||||
|
|
||||||
- `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/aim-core`: source normalization, provider adapters, install/update planning, payload installation, registry persistence, and desktop integration.
|
||||||
- `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/aim-cli`: argument parsing, config loading, terminal UX, prompting, progress reporting, and summary rendering.
|
||||||
- `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 intended split is strict:
|
The split keeps product logic in `aim-core` so additional frontends can reuse the same install and update pipeline.
|
||||||
|
|
||||||
- `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
|
## Core Flow
|
||||||
|
|
||||||
The main execution path is:
|
The main execution path is:
|
||||||
|
|
||||||
1. Parse CLI input and load runtime config in `upm`.
|
1. Parse CLI input and load runtime config in `aim-cli`.
|
||||||
2. Call the unified application facade in `upm-core`.
|
2. Resolve the query into a normalized source in `aim-core`.
|
||||||
3. Let `upm-core` route the request into internal orchestration services.
|
3. Build an add or update plan through provider adapters and artifact selection.
|
||||||
4. Let those services select enabled modules and fan the request out through normalized module traits.
|
4. Download the selected AppImage into a staged path.
|
||||||
5. Aggregate normalized results into an add, show, update, search, or remove flow.
|
5. Verify integrity metadata when available.
|
||||||
6. Download the selected AppImage into a staged path when the chosen module requires it.
|
6. Commit the payload into the managed install location.
|
||||||
7. Verify integrity metadata when available.
|
7. Write desktop integration artifacts and refresh helper caches.
|
||||||
8. Commit the payload into the managed install location.
|
8. Persist registry state atomically.
|
||||||
9. Write desktop integration artifacts and refresh helper caches.
|
|
||||||
10. Persist registry state atomically.
|
|
||||||
|
|
||||||
## Source And Provider Model
|
## Source And Provider Model
|
||||||
|
|
||||||
|
|
@ -100,18 +33,7 @@ Supported source classes currently include:
|
||||||
- direct URLs
|
- direct URLs
|
||||||
- local file imports
|
- local file imports
|
||||||
|
|
||||||
Core orchestration and normalized module contracts live in `crates/upm-core`. Package-manager-specific behavior belongs in module crates.
|
Provider-specific resolution lives in `crates/aim-core/src/adapters` and `crates/aim-core/src/source`.
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
The rename to `upm` is a hard cutover:
|
|
||||||
|
|
||||||
- runtime overrides use `UPM_*`
|
|
||||||
- legacy `AIM_*` runtime overrides are not read
|
|
||||||
- default config, registry, payload, and desktop-entry paths use `upm` names
|
|
||||||
- helper audit logging uses `UPM_DEBUG_EXTERNAL_HELPERS=1`
|
|
||||||
|
|
||||||
## Security Hardening State
|
## Security Hardening State
|
||||||
|
|
||||||
|
|
@ -129,9 +51,9 @@ The remaining deferred AppImageHub host-trust concern is tracked in `security-is
|
||||||
|
|
||||||
## Persistence And Integration
|
## Persistence And Integration
|
||||||
|
|
||||||
- Registry writes are atomic and live under the registry store implementation in `upm-core`.
|
- Registry writes are atomic and live under the registry store implementation in `aim-core`.
|
||||||
- Managed payload, desktop entry, and icon paths are resolved from install policy and scope.
|
- Managed payload, desktop entry, and icon paths are resolved from install policy and scope.
|
||||||
- Desktop integration refresh uses external helpers when available and now supports env-gated audit logging through `UPM_DEBUG_EXTERNAL_HELPERS=1`.
|
- Desktop integration refresh uses external helpers when available and now supports env-gated audit logging through `AIM_DEBUG_EXTERNAL_HELPERS=1`.
|
||||||
|
|
||||||
## Planning And Audit Artifacts
|
## Planning And Audit Artifacts
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
# UPM Roadmap
|
|
||||||
|
|
||||||
## Direction
|
|
||||||
|
|
||||||
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 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.
|
|
||||||
|
|
||||||
## Confirmed Product Decisions
|
|
||||||
|
|
||||||
- `aim-cli` becomes `upm`.
|
|
||||||
- `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 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.
|
|
||||||
- Phase 2 is Linux implementation first, with macOS-oriented provider abstractions and packaging seams designed now.
|
|
||||||
- Feature delivery should happen as vertical slices rather than a single large refactor.
|
|
||||||
- The `upm` branch is the effective trunk for this evolution work and should be treated as the integration base for future UPM feature branches and worktrees.
|
|
||||||
|
|
||||||
## Architectural Destination
|
|
||||||
|
|
||||||
### Workspace Shape
|
|
||||||
|
|
||||||
The intended workspace shape after the initial refactor is:
|
|
||||||
|
|
||||||
- `upm`: thin CLI frontend, ratatui config UI, command routing, presentation.
|
|
||||||
- `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
|
|
||||||
|
|
||||||
UPM should stay modular in both code and packaging:
|
|
||||||
|
|
||||||
- modules can be enabled or disabled by config
|
|
||||||
- providers can be ranked by user priority
|
|
||||||
- distro packaging can offer grouped installs such as `upm-full`
|
|
||||||
- lighter installs can ship only the core and selected modules
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
The long-term model is declarative and config-first, but Phase 2 begins with a hybrid approach:
|
|
||||||
|
|
||||||
- `upm`-managed actions update the declarative config directly
|
|
||||||
- `upm update` and normal command entrypoints can inspect live system state
|
|
||||||
- drift detection reconciles unmanaged or changed packages into the config representation
|
|
||||||
- over time the config becomes authoritative and reconciliation becomes stricter
|
|
||||||
|
|
||||||
## Phase 2 Milestones
|
|
||||||
|
|
||||||
### Milestone 0: Rename And Split Foundation
|
|
||||||
|
|
||||||
Deliver the naming and ownership transition without changing product scope yet.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- rename workspace crates and package outputs from `aim` to `upm`
|
|
||||||
- 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`
|
|
||||||
- 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:
|
|
||||||
|
|
||||||
- `upm` binary replaces `aim`
|
|
||||||
- workspace builds under new crate names
|
|
||||||
- AppImage flows still work end-to-end through the new layering
|
|
||||||
|
|
||||||
### Milestone 1: AppImage On The New Core
|
|
||||||
|
|
||||||
Make the current AppImage implementation the first real package-manager module on the modular architecture.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- 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
|
|
||||||
- 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
|
|
||||||
|
|
||||||
Add the first non-AppImage providers as Linux-focused vertical slices.
|
|
||||||
|
|
||||||
Initial supported sources:
|
|
||||||
|
|
||||||
- pacman
|
|
||||||
- AUR
|
|
||||||
- Flatpak
|
|
||||||
- cargo global installs
|
|
||||||
- npm global installs
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- implement provider discovery and capability coverage for each source
|
|
||||||
- normalize package identity, version, installed state, and update candidates across providers
|
|
||||||
- support search, inspect, install, remove, and update planning where each provider can do so safely
|
|
||||||
- capture provider limitations explicitly rather than faking uniformity
|
|
||||||
|
|
||||||
Exit criteria:
|
|
||||||
|
|
||||||
- multi-source search works across enabled providers
|
|
||||||
- installs and removals work through a consistent command model
|
|
||||||
- provider-specific metadata is normalized enough for ranking and sync
|
|
||||||
|
|
||||||
### Milestone 3: Provider Priority And Config UX
|
|
||||||
|
|
||||||
Expose modularity directly in the terminal interface.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- build a ratatui configuration menu
|
|
||||||
- allow enablement and disablement per provider
|
|
||||||
- allow explicit search and install priority ordering
|
|
||||||
- allow configuration of module weight, source preference, and future policy toggles
|
|
||||||
- make search ranking obey configured priority instead of hard-coded source bias
|
|
||||||
|
|
||||||
Exit criteria:
|
|
||||||
|
|
||||||
- users can manage provider selection and ranking from the TUI
|
|
||||||
- search results are explainable in terms of configured preference order
|
|
||||||
|
|
||||||
### Milestone 4: Declarative Package State And Drift Sync
|
|
||||||
|
|
||||||
Introduce the nix-like experience incrementally.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- define the declarative package file format in `config.toml` or a closely related managed file
|
|
||||||
- track installed packages by provider in a normalized state model
|
|
||||||
- implement `upm update` as both package refresh and state reconciliation
|
|
||||||
- scan currently installed packages from supported providers and build or refresh the declared package set
|
|
||||||
- auto-sync detected drift during any `upm` command invocation
|
|
||||||
|
|
||||||
Phase 2 intent:
|
|
||||||
|
|
||||||
- begin in hybrid mode
|
|
||||||
- move steadily toward config-first behavior
|
|
||||||
- avoid destructive reconciliation until provider semantics are trustworthy
|
|
||||||
|
|
||||||
Exit criteria:
|
|
||||||
|
|
||||||
- users can bootstrap a declarative package definition from the current machine state
|
|
||||||
- repeated `upm` runs keep declared and observed state aligned
|
|
||||||
- state drift is surfaced clearly and reconciled predictably
|
|
||||||
|
|
||||||
### Milestone 5: Packaging, Distribution, And Platform Seams
|
|
||||||
|
|
||||||
Make the modular architecture real at packaging time, not just in code.
|
|
||||||
|
|
||||||
Goals:
|
|
||||||
|
|
||||||
- define packaging layout for standalone core, selected modules, and full installs
|
|
||||||
- support distro-level grouped packages such as `upm-full`
|
|
||||||
- ensure unsupported modules degrade cleanly on the wrong OS or distro
|
|
||||||
- add macOS provider and packaging seams to `upm-core` even if Linux remains the only implemented provider set in Phase 2
|
|
||||||
|
|
||||||
Exit criteria:
|
|
||||||
|
|
||||||
- module packaging strategy is documented and testable
|
|
||||||
- cross-platform abstractions exist without blocking Linux delivery
|
|
||||||
|
|
||||||
## Phase 2 Non-Goals
|
|
||||||
|
|
||||||
The following are explicitly not required to complete Phase 2:
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
|
|
||||||
Phase 2 is successful when the project can credibly be described as a modular package manager rather than an AppImage manager with extra adapters.
|
|
||||||
|
|
||||||
That means:
|
|
||||||
|
|
||||||
- the product name, workspace shape, and binary identity are all `upm`
|
|
||||||
- AppImage support is only one module among several
|
|
||||||
- Linux users can manage packages from the first targeted provider set
|
|
||||||
- ranking and enablement are user-controlled through config and TUI
|
|
||||||
- declarative state exists, is importable from the live system, and stays synchronized through normal use
|
|
||||||
- the architecture is ready for GUI and later platform expansion without another major rewrite
|
|
||||||
|
|
||||||
## Immediate Planning Order
|
|
||||||
|
|
||||||
Implementation plans should follow this order:
|
|
||||||
|
|
||||||
1. rename and crate extraction
|
|
||||||
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
|
|
||||||
6. packaging layout and `upm-full` distribution strategy
|
|
||||||
|
|
||||||
This order keeps the refactor defensible, gives each slice a usable product outcome, and avoids locking future provider work into AppImage-era assumptions.
|
|
||||||
|
|
@ -1,410 +0,0 @@
|
||||||
# UPM Rename And Core Extraction Implementation Plan
|
|
||||||
|
|
||||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** Rename the product from `aim` to `upm`, remove legacy `aim` runtime interfaces, extract the shared headless backend into `upm-core`, and move AppImage-specific transport and provider logic into a separate `upm-appimage` module without regressing current AppImage workflows.
|
|
||||||
|
|
||||||
**Architecture:** Execute this in vertical slices. First rename the workspace, binary, paths, environment interfaces, and tests to `upm` without carrying legacy `aim` compatibility. Next introduce a narrow provider-composition seam in `upm-core` so AppImage-specific add and search logic can move into `upm-appimage` without creating a dependency cycle. Finally rewire the `upm` CLI to assemble built-in providers, update docs, and run full verification.
|
|
||||||
|
|
||||||
**Tech Stack:** Rust workspace, Cargo manifests, clap CLI, ratatui frontend crate, core domain/app modules, fixture-backed provider tests, workspace-wide `cargo test` and `cargo clippy`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Rename the workspace, binary, and default runtime paths to `upm`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `Cargo.toml`
|
|
||||||
- Rename: `crates/aim-cli` -> `crates/upm`
|
|
||||||
- Rename: `crates/aim-core` -> `crates/upm-core`
|
|
||||||
- Modify: `crates/upm/Cargo.toml`
|
|
||||||
- Modify: `crates/upm/src/main.rs`
|
|
||||||
- Modify: `crates/upm/src/lib.rs`
|
|
||||||
- Modify: `crates/upm/src/cli/args.rs`
|
|
||||||
- Modify: `crates/upm/src/config.rs`
|
|
||||||
- Modify: `crates/upm/src/cli/config.rs`
|
|
||||||
- Modify: `crates/upm-core/Cargo.toml`
|
|
||||||
- Modify: `crates/upm-core/src/platform/mod.rs`
|
|
||||||
- Modify: `crates/upm-core/src/integration/paths.rs`
|
|
||||||
- Modify: `crates/upm-core/src/integration/policy.rs`
|
|
||||||
- Test: `crates/upm/tests/cli_smoke.rs`
|
|
||||||
- Test: `crates/upm/tests/cli_commands.rs`
|
|
||||||
- Test: `crates/upm/tests/config_loading.rs`
|
|
||||||
- Test: `crates/upm-core/tests/install_paths.rs`
|
|
||||||
- Test: `crates/upm-core/tests/install_policy.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing rename expectations**
|
|
||||||
|
|
||||||
Update the selected tests to assert:
|
|
||||||
|
|
||||||
- the binary name is `upm`
|
|
||||||
- clap parses `upm` instead of `aim`
|
|
||||||
- default config path is `~/.config/upm/config.toml`
|
|
||||||
- default registry path is `~/.local/share/upm/registry.toml`
|
|
||||||
- default managed payload roots are `.local/lib/upm/appimages` and `/opt/upm/appimages`
|
|
||||||
- desktop entry filenames use `upm-<stable-id>.desktop`
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package aim-cli --test cli_smoke
|
|
||||||
cargo test --package aim-cli --test config_loading
|
|
||||||
cargo test --package aim-core --test install_paths
|
|
||||||
cargo test --package aim-core --test install_policy
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because the workspace still exposes `aim`, `aim-cli`, `aim-core`, and `aim` default paths.
|
|
||||||
|
|
||||||
**Step 3: Perform the crate and manifest rename**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git mv crates/aim-cli crates/upm
|
|
||||||
git mv crates/aim-core crates/upm-core
|
|
||||||
```
|
|
||||||
|
|
||||||
Then update:
|
|
||||||
|
|
||||||
- workspace members and default members in `Cargo.toml`
|
|
||||||
- package names to `upm` and `upm-core`
|
|
||||||
- binary name to `upm`
|
|
||||||
- crate imports from `aim_core` to `upm_core`
|
|
||||||
- crate imports from `aim_cli` to `upm`
|
|
||||||
- clap command name from `aim` to `upm`
|
|
||||||
- default config, registry, payload-root, and desktop-entry paths to `upm`
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test cli_smoke
|
|
||||||
cargo test --package upm --test cli_commands
|
|
||||||
cargo test --package upm --test config_loading
|
|
||||||
cargo test --package upm-core --test install_paths
|
|
||||||
cargo test --package upm-core --test install_policy
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add Cargo.toml crates/upm crates/upm-core
|
|
||||||
git commit -m "refactor: rename workspace to upm"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2: Remove remaining `aim`-named runtime interfaces
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm/src/config.rs`
|
|
||||||
- Modify: `crates/upm/src/cli/config.rs`
|
|
||||||
- Modify: `crates/upm/src/lib.rs`
|
|
||||||
- Modify: `crates/upm/src/ui/prompt.rs`
|
|
||||||
- Modify: `crates/upm-core/src/platform/mod.rs`
|
|
||||||
- Modify: `crates/upm-core/src/source/github.rs`
|
|
||||||
- Modify: `crates/upm-core/src/source/appimagehub.rs`
|
|
||||||
- Modify: `crates/upm-core/src/integration/refresh.rs`
|
|
||||||
- Test: `crates/upm/tests/config_loading.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing strict-rename expectations**
|
|
||||||
|
|
||||||
Update representative tests to cover:
|
|
||||||
|
|
||||||
- config lookup uses `UPM_CONFIG_PATH`
|
|
||||||
- registry lookup uses `UPM_REGISTRY_PATH`
|
|
||||||
- old `AIM_*` config and registry overrides are ignored
|
|
||||||
- tracking preference uses `UPM_TRACKING_PREFERENCE`
|
|
||||||
- old `AIM_TRACKING_PREFERENCE` is ignored
|
|
||||||
- provider fixture execution uses the renamed `UPM_*` interfaces through CLI-facing tests
|
|
||||||
- managed install and summary output use `upm` paths and desktop prefixes
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test config_loading
|
|
||||||
cargo test --package upm --test search_cli
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because representative CLI and config flows still depend on old `aim` names.
|
|
||||||
|
|
||||||
**Step 3: Remove the remaining `aim` interfaces**
|
|
||||||
|
|
||||||
Update the codebase so renamed runtime interfaces are consistently `upm`:
|
|
||||||
|
|
||||||
- environment variable names use `UPM_*`
|
|
||||||
- helper/debug prefixes print `[upm]`
|
|
||||||
- GitHub user agent identifies as `upm/0.1`
|
|
||||||
- old `aim` compatibility reads are removed instead of preserved
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test config_loading
|
|
||||||
cargo test --package upm --test search_cli
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm/src/config.rs crates/upm/src/cli/config.rs crates/upm/src/lib.rs crates/upm/src/ui/prompt.rs crates/upm-core/src/platform/mod.rs crates/upm-core/src/source/github.rs crates/upm-core/src/source/appimagehub.rs crates/upm-core/src/integration/refresh.rs crates/upm/tests/config_loading.rs
|
|
||||||
git commit -m "refactor: remove remaining aim runtime interfaces"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3: Add a provider-composition seam in `upm-core`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `crates/upm-core/src/app/providers.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/mod.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/add.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/search.rs`
|
|
||||||
- Modify: `crates/upm-core/src/lib.rs`
|
|
||||||
- Create: `crates/upm-core/tests/provider_registry.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing provider-composition tests**
|
|
||||||
|
|
||||||
Create `crates/upm-core/tests/provider_registry.rs` with two focused tests:
|
|
||||||
|
|
||||||
- `build_search_results_with_registered_providers_uses_external_hits` using a stub external search provider
|
|
||||||
- `build_add_plan_with_registered_providers_delegates_appimagehub_like_sources` using a stub external add provider that returns a fixed artifact and release
|
|
||||||
|
|
||||||
The tests should prove that `upm-core` orchestration can consume provider-supplied search and add behavior without hardcoding AppImage-specific modules.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-core --test provider_registry
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because the orchestration layer still hardcodes AppImageHub in `app/add.rs` and `app/search.rs`.
|
|
||||||
|
|
||||||
**Step 3: Introduce the narrow composition API**
|
|
||||||
|
|
||||||
Create `crates/upm-core/src/app/providers.rs` with minimal types:
|
|
||||||
|
|
||||||
- `pub trait ExternalAddProvider`
|
|
||||||
- `pub struct ExternalAddResolution`
|
|
||||||
- `pub struct ProviderRegistry<'a>`
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
|
|
||||||
- `ProviderRegistry` carries `search_providers: Vec<&'a dyn SearchProvider>` and `external_add_providers: Vec<&'a dyn ExternalAddProvider>`
|
|
||||||
- `build_search_results` can delegate to `build_search_results_with` using providers supplied by the caller
|
|
||||||
- `build_add_plan_with_reporter_and_policy` gets a sibling entrypoint that accepts a `ProviderRegistry`
|
|
||||||
- core built-ins remain in `upm-core`; only AppImage-specific exact-resolution and search logic should move behind the new registry seam
|
|
||||||
|
|
||||||
Keep the interface intentionally small. Do not attempt plugin loading or dynamic discovery yet.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-core --test provider_registry
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm-core/src/app/providers.rs crates/upm-core/src/app/mod.rs crates/upm-core/src/app/add.rs crates/upm-core/src/app/search.rs crates/upm-core/src/lib.rs crates/upm-core/tests/provider_registry.rs
|
|
||||||
git commit -m "refactor: add provider composition seam to upm-core"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 4: Extract AppImage-specific logic into `upm-appimage`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `Cargo.toml`
|
|
||||||
- Create: `crates/upm-appimage/Cargo.toml`
|
|
||||||
- Create: `crates/upm-appimage/src/lib.rs`
|
|
||||||
- Create: `crates/upm-appimage/src/add.rs`
|
|
||||||
- Create: `crates/upm-appimage/src/search.rs`
|
|
||||||
- Create: `crates/upm-appimage/src/source/mod.rs`
|
|
||||||
- Create: `crates/upm-appimage/src/source/appimagehub.rs`
|
|
||||||
- Modify: `crates/upm-core/src/adapters/mod.rs`
|
|
||||||
- Modify: `crates/upm-core/src/source/mod.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/add.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/search.rs`
|
|
||||||
- Create: `crates/upm-appimage/tests/appimagehub_search.rs`
|
|
||||||
- Modify: `crates/upm-core/tests/adapter_contract.rs`
|
|
||||||
- Modify: `crates/upm-core/tests/adapter_smoke.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing extracted-module test**
|
|
||||||
|
|
||||||
Create `crates/upm-appimage/tests/appimagehub_search.rs` by moving the current AppImageHub search expectations out of `upm-core` and updating imports to target the new crate.
|
|
||||||
|
|
||||||
Also update the affected `upm-core` tests so they no longer import `AppImageHubAdapter` from `upm-core` directly.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-appimage --test appimagehub_search
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because the new crate does not exist yet.
|
|
||||||
|
|
||||||
**Step 3: Create the new crate and move AppImageHub implementation into it**
|
|
||||||
|
|
||||||
Move the AppImageHub-specific code into the new crate:
|
|
||||||
|
|
||||||
- transport and fixture logic from `upm-core/src/source/appimagehub.rs`
|
|
||||||
- AppImage-backed exact-resolution logic into `crates/upm-appimage/src/add.rs` implementing `ExternalAddProvider`
|
|
||||||
- AppImageHub search provider logic out of `upm-core/src/app/search.rs` into `crates/upm-appimage/src/search.rs`
|
|
||||||
|
|
||||||
`upm-appimage` should depend on `upm-core`, not the other way around.
|
|
||||||
|
|
||||||
Leave `SourceKind::AppImageHub`, `SourceInputKind::AppImageHub*`, and `NormalizedSourceKind::AppImageHub` in `upm-core` for this milestone. The deeper provider/domain generalization belongs to the next milestone.
|
|
||||||
|
|
||||||
**Step 4: Remove direct AppImageHub wiring from `upm-core`**
|
|
||||||
|
|
||||||
Update `upm-core` so it no longer declares:
|
|
||||||
|
|
||||||
- `pub mod appimagehub;` in `src/adapters/mod.rs`
|
|
||||||
- `pub mod appimagehub;` in `src/source/mod.rs`
|
|
||||||
- built-in AppImageHub search-provider construction in `src/app/search.rs`
|
|
||||||
- direct `AppImageHubAdapter` imports in `src/app/add.rs`
|
|
||||||
|
|
||||||
After this step, AppImage behavior should exist only through the provider registry seam from Task 3.
|
|
||||||
|
|
||||||
**Step 5: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-appimage --test appimagehub_search
|
|
||||||
cargo test --package upm-core --test adapter_contract
|
|
||||||
cargo test --package upm-core --test adapter_smoke
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add Cargo.toml crates/upm-appimage crates/upm-core/src/adapters/mod.rs crates/upm-core/src/source/mod.rs crates/upm-core/src/app/add.rs crates/upm-core/src/app/search.rs crates/upm-core/tests/adapter_contract.rs crates/upm-core/tests/adapter_smoke.rs
|
|
||||||
git commit -m "refactor: extract appimage support into upm-appimage"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 5: Rewire the `upm` CLI to assemble built-in providers from modules
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm/Cargo.toml`
|
|
||||||
- Create: `crates/upm/src/providers.rs`
|
|
||||||
- Modify: `crates/upm/src/lib.rs`
|
|
||||||
- Test: `crates/upm/tests/search_cli.rs`
|
|
||||||
- Test: `crates/upm/tests/end_to_end_cli.rs`
|
|
||||||
- Test: `crates/upm/tests/ui_summary.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing CLI integration expectations**
|
|
||||||
|
|
||||||
Update CLI integration tests to prove that:
|
|
||||||
|
|
||||||
- `upm search firefox` still includes AppImageHub results
|
|
||||||
- direct `upm appimagehub/2338455` install flow still succeeds through the CLI
|
|
||||||
- the final summary output still renders the new `upm`-prefixed paths and desktop-entry names
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test search_cli
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because `upm` does not yet assemble AppImage providers through the extracted module.
|
|
||||||
|
|
||||||
**Step 3: Add CLI-side provider assembly**
|
|
||||||
|
|
||||||
Create `crates/upm/src/providers.rs` that:
|
|
||||||
|
|
||||||
- builds the `ProviderRegistry` for `upm-core`
|
|
||||||
- registers the `upm-appimage` search provider
|
|
||||||
- registers the `upm-appimage` external add provider
|
|
||||||
|
|
||||||
Update `crates/upm/src/lib.rs` so dispatch paths call the provider-aware core entrypoints instead of hardcoded core defaults.
|
|
||||||
|
|
||||||
Do not move progress rendering or config loading into `upm-core`; the CLI remains the presentation layer.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test search_cli
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm/Cargo.toml crates/upm/src/providers.rs crates/upm/src/lib.rs crates/upm/tests/search_cli.rs crates/upm/tests/end_to_end_cli.rs crates/upm/tests/ui_summary.rs
|
|
||||||
git commit -m "refactor: compose providers from upm modules"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 6: Update docs and run full workspace verification
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `README.md`
|
|
||||||
- Modify: `.architecture/overview.md`
|
|
||||||
- Modify: `.architecture/roadmap.md`
|
|
||||||
|
|
||||||
**Step 1: Update product and architecture docs**
|
|
||||||
|
|
||||||
Document:
|
|
||||||
|
|
||||||
- the workspace rename to `upm`
|
|
||||||
- `upm-core` as the headless application layer
|
|
||||||
- `upm-appimage` as the first installable provider module
|
|
||||||
- compatibility behavior for existing `aim` config and registry locations
|
|
||||||
- the fact that provider composition now happens in the CLI rather than through hardcoded AppImage paths in `upm-core`
|
|
||||||
|
|
||||||
**Step 2: Verify the docs mention the new structure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rg -n "upm-core|upm-appimage|legacy aim|ProviderRegistry|upm" README.md .architecture/overview.md .architecture/roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: matches showing the renamed crates, provider split, and compatibility note.
|
|
||||||
|
|
||||||
**Step 3: Run the full verification suite**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --all
|
|
||||||
cargo test --workspace
|
|
||||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add README.md .architecture/overview.md .architecture/roadmap.md
|
|
||||||
git commit -m "docs: describe upm core and module split"
|
|
||||||
```
|
|
||||||
|
|
@ -1,288 +0,0 @@
|
||||||
# AppImage On The New Core Implementation Plan
|
|
||||||
|
|
||||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** Make `upm-appimage` the first real package-manager module by moving AppImage-specific acquisition paths behind a module boundary and exposing a single application facade from `upm-core` to the CLI and future GUI.
|
|
||||||
|
|
||||||
**Architecture:** `upm-core` becomes the application boundary. It should own a public facade, internal orchestration services, and module registration and composition. `upm` stays a thin frontend and must stop composing AppImage behavior directly. `upm-appimage` becomes the AppImage package-manager module and should absorb AppImageHub plus the other AppImage-producing backends that are still modeled as top-level source concepts in the core.
|
|
||||||
|
|
||||||
**Tech Stack:** Rust workspace, `upm`, `upm-core`, `upm-appimage`, Cargo integration tests, fixture-backed provider tests, CLI end-to-end tests.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Define the public application facade in `upm-core`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm-core/src/app/`
|
|
||||||
- Modify: `crates/upm-core/src/lib.rs`
|
|
||||||
- Test: `crates/upm-core/tests/`
|
|
||||||
|
|
||||||
**Step 1: Write the failing facade expectations**
|
|
||||||
|
|
||||||
Add focused tests proving that:
|
|
||||||
|
|
||||||
- `upm-core` exposes one public application-facing entrypoint for frontend consumers
|
|
||||||
- that entrypoint can be constructed without the CLI owning module composition
|
|
||||||
- the public surface delegates to internal services instead of exposing module wiring details
|
|
||||||
|
|
||||||
Keep the assertions about API shape and ownership, not AppImage specifics.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-core
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because the current public surface still assumes narrower provider plumbing and does not expose the intended facade cleanly.
|
|
||||||
|
|
||||||
**Step 3: Implement the minimal facade**
|
|
||||||
|
|
||||||
Introduce or reshape the public API so `upm-core` exposes a single high-level application facade.
|
|
||||||
|
|
||||||
The public boundary should:
|
|
||||||
|
|
||||||
- represent product operations such as search, add, show, update, remove, and config handling
|
|
||||||
- hide module registry and orchestration details from frontends
|
|
||||||
- stay thin and delegate work to internal services
|
|
||||||
|
|
||||||
Do not add dynamic plugin loading yet.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-core
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm-core/src crates/upm-core/tests
|
|
||||||
git commit -m "feat: add application facade to upm-core"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2: Move module composition out of the CLI and into `upm-core`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm-core/src/app/`
|
|
||||||
- Modify: `crates/upm/src/lib.rs`
|
|
||||||
- Modify: `crates/upm/src/providers.rs`
|
|
||||||
- Test: `crates/upm-core/tests/`
|
|
||||||
- Test: `crates/upm/tests/end_to_end_cli.rs`
|
|
||||||
|
|
||||||
**Step 1: Write the failing ownership expectations**
|
|
||||||
|
|
||||||
Add coverage proving that:
|
|
||||||
|
|
||||||
- the CLI does not assemble AppImage module composition directly
|
|
||||||
- the application facade can build or receive module composition internally
|
|
||||||
- CLI command paths still behave the same through the new boundary
|
|
||||||
|
|
||||||
Prefer one core ownership test and one CLI integration test.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because the CLI still owns direct provider assembly.
|
|
||||||
|
|
||||||
**Step 3: Move composition into `upm-core`**
|
|
||||||
|
|
||||||
Update the architecture so:
|
|
||||||
|
|
||||||
- `upm-core` owns module registration and composition
|
|
||||||
- the CLI constructs the application facade rather than AppImage-specific registries
|
|
||||||
- direct module composition in `crates/upm/src/providers.rs` is removed or reduced to generic application bootstrapping
|
|
||||||
|
|
||||||
Keep CLI UX, rendering, and summary formatting unchanged.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm-core/src crates/upm/src/lib.rs crates/upm/src/providers.rs crates/upm/tests/end_to_end_cli.rs
|
|
||||||
git commit -m "refactor: move module composition into upm-core"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3: Turn `upm-appimage` into the AppImage package-manager boundary
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm-appimage/src/`
|
|
||||||
- Modify: `crates/upm-core/src/domain/source.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/add.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/show.rs`
|
|
||||||
- Modify: `crates/upm-core/src/app/update.rs`
|
|
||||||
- Test: `crates/upm-appimage/tests/`
|
|
||||||
- Test: `crates/upm-core/tests/`
|
|
||||||
|
|
||||||
**Step 1: Write the failing module-boundary expectations**
|
|
||||||
|
|
||||||
Add coverage proving that:
|
|
||||||
|
|
||||||
- AppImage-backed acquisition through GitHub, GitLab, SourceForge, direct URLs, and AppImageHub resolves through `upm-appimage`
|
|
||||||
- `upm-core` no longer treats those AppImage-producing backends as top-level package-manager concepts
|
|
||||||
- add, show, and update continue to work through normalized module contracts
|
|
||||||
|
|
||||||
Prefer module-focused tests plus a small number of core integration tests.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-appimage
|
|
||||||
cargo test --package upm-core
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL because AppImage acquisition paths are still split between the core and the AppImage module.
|
|
||||||
|
|
||||||
**Step 3: Move AppImage-specific acquisition logic behind the module**
|
|
||||||
|
|
||||||
Reshape the source and module boundary so:
|
|
||||||
|
|
||||||
- AppImage-specific GitHub, GitLab, SourceForge, AppImageHub, and direct URL handling lives in `upm-appimage`
|
|
||||||
- `upm-core` coordinates AppImage work through normalized module contracts
|
|
||||||
- core source taxonomy is reduced or reframed so package-manager concepts stay above backend minutia
|
|
||||||
|
|
||||||
Do not over-generalize for Flatpak or future providers yet. Only extract what the AppImage module demonstrably needs.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm-appimage
|
|
||||||
cargo test --package upm-core
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm-appimage/src crates/upm-core/src crates/upm-appimage/tests crates/upm-core/tests
|
|
||||||
git commit -m "refactor: make upm-appimage the appimage module boundary"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 4: Route search, add, show, and update through the application facade
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `crates/upm-core/src/app/`
|
|
||||||
- Modify: `crates/upm/src/lib.rs`
|
|
||||||
- Modify: `crates/upm/tests/end_to_end_cli.rs`
|
|
||||||
- Modify: `crates/upm/tests/ui_summary.rs`
|
|
||||||
- Test: `crates/upm-core/tests/`
|
|
||||||
|
|
||||||
**Step 1: Write the failing facade-routing expectations**
|
|
||||||
|
|
||||||
Add end-to-end coverage proving that AppImage support is fully module-driven:
|
|
||||||
|
|
||||||
- `search` flows through the public application facade
|
|
||||||
- `add` flows through the public application facade
|
|
||||||
- `show` flows through the public application facade
|
|
||||||
- `update` flows through the public application facade
|
|
||||||
- user-facing summaries still render truthful `upm` paths and origins
|
|
||||||
|
|
||||||
Keep the assertions focused on boundary correctness rather than UI restyling.
|
|
||||||
|
|
||||||
**Step 2: Run the focused tests to verify failure**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: FAIL until the facade is the normal command path.
|
|
||||||
|
|
||||||
**Step 3: Tighten application-boundary validation**
|
|
||||||
|
|
||||||
Update the tests so they prove:
|
|
||||||
|
|
||||||
- frontend command handlers call the application facade rather than module-specific helpers
|
|
||||||
- AppImage is composed only through `upm-core` and `upm-appimage`
|
|
||||||
- AppImage is not reintroduced as a hardcoded built-in in the CLI
|
|
||||||
|
|
||||||
Do not reintroduce package-manager-specific branching in the CLI.
|
|
||||||
|
|
||||||
**Step 4: Run the focused tests to verify pass**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --package upm --test end_to_end_cli
|
|
||||||
cargo test --package upm --test ui_summary
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 5: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add crates/upm-core/src crates/upm/src/lib.rs crates/upm/tests/end_to_end_cli.rs crates/upm/tests/ui_summary.rs crates/upm-core/tests
|
|
||||||
git commit -m "refactor: route commands through upm-core facade"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 5: Lock the architecture into docs and verify the workspace
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `.architecture/overview.md`
|
|
||||||
- Modify: `.architecture/roadmap.md`
|
|
||||||
- Modify: `README.md`
|
|
||||||
|
|
||||||
**Step 1: Update docs for the new module model**
|
|
||||||
|
|
||||||
Document:
|
|
||||||
|
|
||||||
- `upm-core` as the application boundary
|
|
||||||
- one public application facade over smaller internal services
|
|
||||||
- CLI and GUI as thin frontends
|
|
||||||
- `upm-appimage` as the AppImage package-manager boundary with internal acquisition backends
|
|
||||||
|
|
||||||
**Step 2: Verify the docs mention the agreed architecture**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rg -n "upm-core|facade|upm-ui|upm-appimage|module" README.md .architecture/overview.md .architecture/roadmap.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: matches describing the application boundary, thin frontends, and module ownership.
|
|
||||||
|
|
||||||
**Step 3: Run full verification**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --all
|
|
||||||
cargo test --workspace
|
|
||||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: PASS.
|
|
||||||
|
|
||||||
**Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add README.md .architecture/overview.md .architecture/roadmap.md
|
|
||||||
git commit -m "docs: describe application facade architecture"
|
|
||||||
```
|
|
||||||
|
|
@ -10,8 +10,4 @@ Audits are to live in `.audits` with a good name slug plus time & date.
|
||||||
IMPORTANT TO CHECK BEFORE ANY COMMIT!!
|
IMPORTANT TO CHECK BEFORE ANY COMMIT!!
|
||||||
Architecture under `.architecture` must be maintained with each change.
|
Architecture under `.architecture` must be maintained with each change.
|
||||||
- Security issues stumbled upon or noticed during execution **already in code** must live in `security-issues.md`. Newly added issues during execution or planning should be raised to the user and/or dealt with, instead of growing the list.
|
- Security issues stumbled upon or noticed during execution **already in code** must live in `security-issues.md`. Newly added issues during execution or planning should be raised to the user and/or dealt with, instead of growing the list.
|
||||||
- An overview of the workspace, should live in `overview.md`.
|
- An overview of the workspace, should live in `overview.md`.
|
||||||
- A roadmap of the planned direction & features in `roadmap.md`.
|
|
||||||
|
|
||||||
## UPM work
|
|
||||||
We are currently using the `upm` branch as our "main" for all `upm` work, meaning feature branches/worktrees will merge back into here.
|
|
||||||
91
Cargo.lock
generated
91
Cargo.lock
generated
|
|
@ -11,6 +11,41 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aim-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"aim-core",
|
||||||
|
"assert_cmd",
|
||||||
|
"clap",
|
||||||
|
"console 0.16.3",
|
||||||
|
"crossterm",
|
||||||
|
"dialoguer",
|
||||||
|
"indicatif",
|
||||||
|
"libc",
|
||||||
|
"predicates",
|
||||||
|
"ratatui",
|
||||||
|
"serde",
|
||||||
|
"tempfile",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aim-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"fs2",
|
||||||
|
"md5",
|
||||||
|
"quick-xml",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_yaml",
|
||||||
|
"sha2",
|
||||||
|
"tempfile",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -1931,62 +1966,6 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "upm"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"assert_cmd",
|
|
||||||
"clap",
|
|
||||||
"console 0.16.3",
|
|
||||||
"crossterm",
|
|
||||||
"dialoguer",
|
|
||||||
"indicatif",
|
|
||||||
"libc",
|
|
||||||
"predicates",
|
|
||||||
"ratatui",
|
|
||||||
"serde",
|
|
||||||
"tempfile",
|
|
||||||
"toml",
|
|
||||||
"upm-appimage",
|
|
||||||
"upm-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "upm-appimage"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"quick-xml",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"upm-core",
|
|
||||||
"upm-module-api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "upm-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"fs2",
|
|
||||||
"md5",
|
|
||||||
"quick-xml",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_yaml",
|
|
||||||
"sha2",
|
|
||||||
"tempfile",
|
|
||||||
"toml",
|
|
||||||
"upm-appimage",
|
|
||||||
"upm-module-api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "upm-module-api"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"crates/upm-module-api",
|
"crates/aim-core",
|
||||||
"crates/upm-core",
|
"crates/aim-cli",
|
||||||
"crates/upm-appimage",
|
|
||||||
"crates/upm",
|
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
"crates/upm",
|
"crates/aim-cli",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|
|
||||||
53
README.md
53
README.md
|
|
@ -1,26 +1,24 @@
|
||||||
# upm
|
# aim
|
||||||
Universal Package Manager
|
AppImage Manager
|
||||||
|
|
||||||
`upm` is a Rust Cargo workspace for a modular package manager with a shared headless application core, thin frontend crates, and package-manager modules.
|
`aim` is a Rust Cargo workspace for managing AppImages from multiple source types.
|
||||||
|
|
||||||
## Workspace
|
## Workspace
|
||||||
|
|
||||||
- `crates/upm-core`: headless application layer for query normalization, orchestration, module registration and composition, registry persistence, install/update planning, and the unified frontend-facing API
|
- `crates/aim-core`: business logic, source adapters, registry, install/update planning
|
||||||
- `crates/upm`: thin terminal frontend for argument parsing, config loading, prompting, progress reporting, and summary rendering
|
- `crates/aim-cli`: thin terminal frontend for parsing, prompting, and rendering
|
||||||
- `crates/upm-appimage`: AppImage package-manager module responsible for AppImage-specific acquisition and resolution behavior
|
|
||||||
- `crates/upm-ui` (planned): GUI frontend over `upm-core`
|
|
||||||
|
|
||||||
The split is intentional so future frontends can reuse `upm-core`, while package-manager behavior stays modular instead of being hardcoded into the core or the CLI.
|
The split is intentional so a future GUI client can reuse `aim-core` without moving logic out of the shared library.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```text
|
```text
|
||||||
upm <QUERY>
|
aim <QUERY>
|
||||||
upm
|
aim
|
||||||
upm update
|
aim update
|
||||||
upm list
|
aim list
|
||||||
upm search <QUERY>
|
aim search <QUERY>
|
||||||
upm remove <QUERY>
|
aim remove <QUERY>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Query Forms
|
## Query Forms
|
||||||
|
|
@ -38,22 +36,22 @@ upm remove <QUERY>
|
||||||
|
|
||||||
## Search
|
## Search
|
||||||
|
|
||||||
`upm search <QUERY>` is part of the initial modular module surface.
|
`aim search <QUERY>` is part of v0.9 finalisation.
|
||||||
|
|
||||||
- search is module-extensible and currently includes the built-in core search path plus AppImage-backed search sources
|
- search is provider-extensible and currently includes GitHub plus AppImageHub
|
||||||
- search results should resolve to install-ready queries such as `owner/repo` and `appimagehub/<id>`
|
- search results should resolve to install-ready queries such as `owner/repo` and `appimagehub/<id>`
|
||||||
- module composition belongs in `upm-core`, not in the CLI frontend
|
- the search model is provider-extensible for future phases
|
||||||
|
|
||||||
## Scope Overrides
|
## Scope Overrides
|
||||||
|
|
||||||
By default `upm` auto-detects whether to use user or system scope. Override that with:
|
By default `aim` auto-detects whether to use user or system scope. Override that with:
|
||||||
|
|
||||||
- `--user`
|
- `--user`
|
||||||
- `--system`
|
- `--system`
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
Runtime config is loaded from `~/.config/upm/config.toml` or `$XDG_CONFIG_HOME/upm/config.toml`.
|
Runtime config is loaded from `~/.config/aim/config.toml` or `$XDG_CONFIG_HOME/aim/config.toml`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|
@ -65,20 +63,13 @@ allow_http = true
|
||||||
- `allow_http` only permits user-supplied `http://` inputs such as direct URL installs or updates from previously installed direct HTTP origins
|
- `allow_http` only permits user-supplied `http://` inputs such as direct URL installs or updates from previously installed direct HTTP origins
|
||||||
- provider-resolved downloads such as AppImageHub artifacts remain HTTPS-only even when `allow_http = true`
|
- provider-resolved downloads such as AppImageHub artifacts remain HTTPS-only even when `allow_http = true`
|
||||||
|
|
||||||
## Breaking Rename
|
|
||||||
|
|
||||||
- `upm` is a hard rename from `aim`
|
|
||||||
- runtime overrides now use `UPM_*` names such as `UPM_CONFIG_PATH` and `UPM_REGISTRY_PATH`
|
|
||||||
- old `AIM_*` runtime overrides are intentionally ignored
|
|
||||||
- default config and registry locations now live under `upm` paths
|
|
||||||
|
|
||||||
## Current Flow Shape
|
## Current Flow Shape
|
||||||
|
|
||||||
- `upm <QUERY>` installs direct provider matches when available, otherwise falls back to search results, shows live progress on stderr, prints an `Installation Summary` on stdout for installs, and renders an `Installation Review` when tracking needs confirmation
|
- `aim <QUERY>` installs direct provider matches when available, otherwise falls back to search results, shows live progress on stderr, prints an `Installation Summary` on stdout for installs, and renders an `Installation Review` when tracking needs confirmation
|
||||||
- bare `upm` prints an `Update Review` without mutating the registry
|
- bare `aim` prints an `Update Review` without mutating the registry
|
||||||
- `upm update` executes the pending updates, streams live status on stderr, then prints an `Update Summary`
|
- `aim update` executes the pending updates, streams live status on stderr, then prints an `Update Summary`
|
||||||
- `upm list` renders either `Installed Apps` or `No installed apps yet`
|
- `aim list` renders either `Installed Apps` or `No installed apps yet`
|
||||||
- `upm remove <QUERY>` resolves a registered application name, streams removal progress on stderr, then prints a `Removal Summary`
|
- `aim remove <QUERY>` resolves a registered application name, streams removal progress on stderr, then prints a `Removal Summary`
|
||||||
|
|
||||||
## Terminal UX
|
## Terminal UX
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "upm"
|
name = "aim-cli"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
@ -8,7 +8,7 @@ license.workspace = true
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "upm"
|
name = "aim"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -21,8 +21,7 @@ libc.workspace = true
|
||||||
ratatui.workspace = true
|
ratatui.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
upm-appimage = { path = "../upm-appimage" }
|
aim-core = { path = "../aim-core" }
|
||||||
upm-core = { path = "../upm-core" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd.workspace = true
|
assert_cmd.workspace = true
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "upm")]
|
#[command(name = "aim")]
|
||||||
#[command(about = "Universal Package Manager")]
|
#[command(about = "AppImage Manager")]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[arg(global = true, long = "system", conflicts_with = "user")]
|
#[arg(global = true, long = "system", conflicts_with = "user")]
|
||||||
pub system: bool,
|
pub system: bool,
|
||||||
|
|
@ -52,10 +52,10 @@ struct FileThemeConfig {
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub fn load() -> LoadedConfig {
|
pub fn load() -> LoadedConfig {
|
||||||
let system_path = Some(PathBuf::from("/etc/upm/config.toml"));
|
let system_path = Some(PathBuf::from("/etc/aim/config.toml"));
|
||||||
let user_path = env::var_os("HOME")
|
let user_path = env::var_os("HOME")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.map(|home| home.join(".config/upm/config.toml"));
|
.map(|home| home.join(".config/aim/config.toml"));
|
||||||
Self::load_from_paths(system_path.as_deref(), user_path.as_deref())
|
Self::load_from_paths(system_path.as_deref(), user_path.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,16 +68,16 @@ pub fn load_from_path(path: &Path) -> Result<CliConfig, ConfigError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_path() -> PathBuf {
|
pub fn default_path() -> PathBuf {
|
||||||
if let Some(path) = env::var_os("UPM_CONFIG_PATH") {
|
if let Some(path) = env::var_os("AIM_CONFIG_PATH") {
|
||||||
return PathBuf::from(path);
|
return PathBuf::from(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(config_home) = env::var_os("XDG_CONFIG_HOME") {
|
if let Some(config_home) = env::var_os("XDG_CONFIG_HOME") {
|
||||||
return PathBuf::from(config_home).join("upm/config.toml");
|
return PathBuf::from(config_home).join("aim/config.toml");
|
||||||
}
|
}
|
||||||
|
|
||||||
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
|
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
|
||||||
PathBuf::from(home).join(".config/upm/config.toml")
|
PathBuf::from(home).join(".config/aim/config.toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -1,23 +1,28 @@
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod providers;
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use upm_core::app::add::{AddPlan, AddSecurityPolicy, InstalledApp, resolve_requested_scope};
|
use aim_core::app::add::{
|
||||||
use upm_core::app::list::ListRow;
|
AddPlan, AddSecurityPolicy, InstalledApp, build_add_plan_with_reporter_and_policy,
|
||||||
use upm_core::app::progress::{
|
install_app_with_reporter, resolve_requested_scope,
|
||||||
|
};
|
||||||
|
use aim_core::app::list::{ListRow, build_list_rows};
|
||||||
|
use aim_core::app::progress::{
|
||||||
NoopReporter, OperationEvent, OperationKind, OperationStage, ProgressReporter,
|
NoopReporter, OperationEvent, OperationKind, OperationStage, ProgressReporter,
|
||||||
};
|
};
|
||||||
use upm_core::app::remove::{RemovalResult, remove_registered_app_with_reporter};
|
use aim_core::app::remove::{RemovalResult, remove_registered_app_with_reporter};
|
||||||
use upm_core::domain::app::AppRecord;
|
use aim_core::app::search::build_search_results;
|
||||||
use upm_core::domain::search::{SearchQuery, SearchResults};
|
use aim_core::app::show::{build_installed_show_results, build_show_result};
|
||||||
use upm_core::domain::show::{InstalledShow, ShowResult};
|
use aim_core::app::update::{build_update_plan, execute_updates_with_reporter_and_policy};
|
||||||
use upm_core::domain::update::{UpdateExecutionResult, UpdatePlan};
|
use aim_core::domain::app::AppRecord;
|
||||||
use upm_core::registry::store::RegistryStore;
|
use aim_core::domain::search::{SearchQuery, SearchResults};
|
||||||
|
use aim_core::domain::show::{InstalledShow, ShowResult};
|
||||||
|
use aim_core::domain::update::{UpdateExecutionResult, UpdatePlan};
|
||||||
|
use aim_core::registry::store::RegistryStore;
|
||||||
|
|
||||||
pub use cli::args::Cli;
|
pub use cli::args::Cli;
|
||||||
|
|
||||||
|
|
@ -47,15 +52,14 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
let store = RegistryStore::new(registry_path);
|
let store = RegistryStore::new(registry_path);
|
||||||
let registry = store.load()?;
|
let registry = store.load()?;
|
||||||
let apps = registry.apps.clone();
|
let apps = registry.apps.clone();
|
||||||
let app = providers::application();
|
|
||||||
|
|
||||||
if cli.is_review_update_flow() {
|
if cli.is_review_update_flow() {
|
||||||
return Ok(DispatchResult::UpdatePlan(app.build_update_plan(&apps)?));
|
return Ok(DispatchResult::UpdatePlan(build_update_plan(&apps)?));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(command) = cli.command {
|
if let Some(command) = cli.command {
|
||||||
return match command {
|
return match command {
|
||||||
cli::args::Command::List => Ok(DispatchResult::List(app.list(&apps))),
|
cli::args::Command::List => Ok(DispatchResult::List(build_list_rows(&apps))),
|
||||||
cli::args::Command::Remove { query } => {
|
cli::args::Command::Remove { query } => {
|
||||||
let removal =
|
let removal =
|
||||||
remove_registered_app_with_reporter(&query, &apps, &install_home, reporter)?;
|
remove_registered_app_with_reporter(&query, &apps, &install_home, reporter)?;
|
||||||
|
|
@ -76,7 +80,7 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
kind: OperationKind::Search,
|
kind: OperationKind::Search,
|
||||||
label: query.clone(),
|
label: query.clone(),
|
||||||
});
|
});
|
||||||
let results = app.search(&SearchQuery::new(&query), &apps)?;
|
let results = build_search_results(&SearchQuery::new(&query), &apps)?;
|
||||||
reporter.report(&OperationEvent::Finished {
|
reporter.report(&OperationEvent::Finished {
|
||||||
summary: format!("search complete: {} remote hits", results.remote_hits.len()),
|
summary: format!("search complete: {} remote hits", results.remote_hits.len()),
|
||||||
});
|
});
|
||||||
|
|
@ -84,13 +88,13 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
}
|
}
|
||||||
cli::args::Command::Show { value } => match value {
|
cli::args::Command::Show { value } => match value {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
let result = app.show(&value, &apps)?;
|
let result = build_show_result(&value, &apps)?;
|
||||||
Ok(DispatchResult::Show(Box::new(result)))
|
Ok(DispatchResult::Show(Box::new(result)))
|
||||||
}
|
}
|
||||||
None => Ok(DispatchResult::ShowAll(app.show_all(&apps))),
|
None => Ok(DispatchResult::ShowAll(build_installed_show_results(&apps))),
|
||||||
},
|
},
|
||||||
cli::args::Command::Update => {
|
cli::args::Command::Update => {
|
||||||
let updates = app.execute_updates(
|
let updates = execute_updates_with_reporter_and_policy(
|
||||||
&apps,
|
&apps,
|
||||||
&install_home,
|
&install_home,
|
||||||
reporter,
|
reporter,
|
||||||
|
|
@ -119,8 +123,10 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
|
|
||||||
if let Some(query) = cli.query {
|
if let Some(query) = cli.query {
|
||||||
let requested_scope = resolve_requested_scope(cli.system, cli.user, is_effective_root());
|
let requested_scope = resolve_requested_scope(cli.system, cli.user, is_effective_root());
|
||||||
let plan_result = app.build_add_plan_with_reporter(
|
let transport = aim_core::source::github::default_transport();
|
||||||
|
let plan_result = build_add_plan_with_reporter_and_policy(
|
||||||
&query,
|
&query,
|
||||||
|
transport.as_ref(),
|
||||||
reporter,
|
reporter,
|
||||||
AddSecurityPolicy {
|
AddSecurityPolicy {
|
||||||
allow_http_user_sources: config.allow_http,
|
allow_http_user_sources: config.allow_http,
|
||||||
|
|
@ -129,16 +135,16 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
let mut plan = match plan_result {
|
let mut plan = match plan_result {
|
||||||
Ok(plan) => plan,
|
Ok(plan) => plan,
|
||||||
Err(
|
Err(
|
||||||
upm_core::app::add::BuildAddPlanError::Query(
|
aim_core::app::add::BuildAddPlanError::Query(
|
||||||
upm_core::app::query::ResolveQueryError::Unsupported,
|
aim_core::app::query::ResolveQueryError::Unsupported,
|
||||||
)
|
)
|
||||||
| upm_core::app::add::BuildAddPlanError::NoInstallableArtifact { .. },
|
| aim_core::app::add::BuildAddPlanError::NoInstallableArtifact { .. },
|
||||||
) => {
|
) => {
|
||||||
reporter.report(&OperationEvent::Started {
|
reporter.report(&OperationEvent::Started {
|
||||||
kind: OperationKind::Search,
|
kind: OperationKind::Search,
|
||||||
label: query.clone(),
|
label: query.clone(),
|
||||||
});
|
});
|
||||||
let results = app.search(&SearchQuery::new(&query), &apps)?;
|
let results = build_search_results(&SearchQuery::new(&query), &apps)?;
|
||||||
reporter.report(&OperationEvent::Finished {
|
reporter.report(&OperationEvent::Finished {
|
||||||
summary: format!("search complete: {} remote hits", results.remote_hits.len()),
|
summary: format!("search complete: {} remote hits", results.remote_hits.len()),
|
||||||
});
|
});
|
||||||
|
|
@ -155,7 +161,8 @@ pub fn dispatch_with_reporter_and_config(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let installed = app.install_app(&query, &plan, &install_home, requested_scope, reporter)?;
|
let installed =
|
||||||
|
install_app_with_reporter(&query, &plan, &install_home, requested_scope, reporter)?;
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
stage: OperationStage::SaveRegistry,
|
stage: OperationStage::SaveRegistry,
|
||||||
message: "saving registry".to_owned(),
|
message: "saving registry".to_owned(),
|
||||||
|
|
@ -181,17 +188,13 @@ pub fn render_with_config(result: &DispatchResult, config: &config::CliConfig) -
|
||||||
ui::render::render_dispatch_result_with_config(result, config)
|
ui::render::render_dispatch_result_with_config(result, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_registry_path() -> PathBuf {
|
fn registry_path() -> PathBuf {
|
||||||
if let Some(path) = env::var_os("UPM_REGISTRY_PATH") {
|
if let Some(path) = env::var_os("AIM_REGISTRY_PATH") {
|
||||||
return PathBuf::from(path);
|
return PathBuf::from(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
|
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
|
||||||
PathBuf::from(home).join(".local/share/upm/registry.toml")
|
PathBuf::from(home).join(".local/share/aim/registry.toml")
|
||||||
}
|
|
||||||
|
|
||||||
fn registry_path() -> PathBuf {
|
|
||||||
default_registry_path()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
|
@ -210,49 +213,49 @@ pub enum DispatchResult {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DispatchError {
|
pub enum DispatchError {
|
||||||
AddPlan(upm_core::app::add::BuildAddPlanError),
|
AddPlan(aim_core::app::add::BuildAddPlanError),
|
||||||
AddInstall(upm_core::app::add::InstallAppError),
|
AddInstall(aim_core::app::add::InstallAppError),
|
||||||
Prompt(ui::prompt::PromptError),
|
Prompt(ui::prompt::PromptError),
|
||||||
RemovePlan(upm_core::app::remove::RemoveRegisteredAppError),
|
RemovePlan(aim_core::app::remove::RemoveRegisteredAppError),
|
||||||
Registry(upm_core::registry::store::RegistryStoreError),
|
Registry(aim_core::registry::store::RegistryStoreError),
|
||||||
Search(upm_core::app::search::SearchError),
|
Search(aim_core::app::search::SearchError),
|
||||||
Show(upm_core::domain::show::ShowResultError),
|
Show(aim_core::domain::show::ShowResultError),
|
||||||
UpdatePlan(upm_core::app::update::BuildUpdatePlanError),
|
UpdatePlan(aim_core::app::update::BuildUpdatePlanError),
|
||||||
UpdateExecution(upm_core::app::update::ExecuteUpdatesError),
|
UpdateExecution(aim_core::app::update::ExecuteUpdatesError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for DispatchError {
|
impl std::fmt::Display for DispatchError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::AddPlan(error) => match error {
|
Self::AddPlan(error) => match error {
|
||||||
upm_core::app::add::BuildAddPlanError::Query(
|
aim_core::app::add::BuildAddPlanError::Query(
|
||||||
upm_core::app::query::ResolveQueryError::Unsupported,
|
aim_core::app::query::ResolveQueryError::Unsupported,
|
||||||
) => write!(f, "unsupported source query"),
|
) => write!(f, "unsupported source query"),
|
||||||
upm_core::app::add::BuildAddPlanError::InsecureHttpSource { .. } => write!(
|
aim_core::app::add::BuildAddPlanError::InsecureHttpSource { .. } => write!(
|
||||||
f,
|
f,
|
||||||
"insecure HTTP sources are disabled; set allow_http = true to permit them"
|
"insecure HTTP sources are disabled; set allow_http = true to permit them"
|
||||||
),
|
),
|
||||||
upm_core::app::add::BuildAddPlanError::NoInstallableArtifact { source } => write!(
|
aim_core::app::add::BuildAddPlanError::NoInstallableArtifact { source } => write!(
|
||||||
f,
|
f,
|
||||||
"no installable artifact found for {} {}",
|
"no installable artifact found for {} {}",
|
||||||
source.kind.as_str(),
|
source.kind.as_str(),
|
||||||
source.locator
|
source.locator
|
||||||
),
|
),
|
||||||
upm_core::app::add::BuildAddPlanError::Adapter(id, error) => match error {
|
aim_core::app::add::BuildAddPlanError::Adapter(id, error) => match error {
|
||||||
upm_core::adapters::traits::AdapterError::UnsupportedQuery => {
|
aim_core::adapters::traits::AdapterError::UnsupportedQuery => {
|
||||||
write!(f, "{id} does not support this query")
|
write!(f, "{id} does not support this query")
|
||||||
}
|
}
|
||||||
upm_core::adapters::traits::AdapterError::UnsupportedSource => {
|
aim_core::adapters::traits::AdapterError::UnsupportedSource => {
|
||||||
write!(f, "{id} does not support this source")
|
write!(f, "{id} does not support this source")
|
||||||
}
|
}
|
||||||
upm_core::adapters::traits::AdapterError::ResolutionFailed(reason) => {
|
aim_core::adapters::traits::AdapterError::ResolutionFailed(reason) => {
|
||||||
write!(f, "{id} resolution failed: {reason}")
|
write!(f, "{id} resolution failed: {reason}")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upm_core::app::add::BuildAddPlanError::GitHubDiscovery(error) => {
|
aim_core::app::add::BuildAddPlanError::GitHubDiscovery(error) => {
|
||||||
write!(f, "github discovery failed: {error:?}")
|
write!(f, "github discovery failed: {error:?}")
|
||||||
}
|
}
|
||||||
upm_core::app::add::BuildAddPlanError::NoCandidates => {
|
aim_core::app::add::BuildAddPlanError::NoCandidates => {
|
||||||
write!(f, "no installable candidates found")
|
write!(f, "no installable candidates found")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -262,7 +265,7 @@ impl std::fmt::Display for DispatchError {
|
||||||
Self::Registry(error) => write!(f, "registry failed: {error:?}"),
|
Self::Registry(error) => write!(f, "registry failed: {error:?}"),
|
||||||
Self::Search(error) => write!(f, "search failed: {error:?}"),
|
Self::Search(error) => write!(f, "search failed: {error:?}"),
|
||||||
Self::Show(error) => match error {
|
Self::Show(error) => match error {
|
||||||
upm_core::domain::show::ShowResultError::AmbiguousInstalledMatch {
|
aim_core::domain::show::ShowResultError::AmbiguousInstalledMatch {
|
||||||
query,
|
query,
|
||||||
matches,
|
matches,
|
||||||
} => write!(
|
} => write!(
|
||||||
|
|
@ -270,14 +273,14 @@ impl std::fmt::Display for DispatchError {
|
||||||
"multiple installed apps match {query}: {}",
|
"multiple installed apps match {query}: {}",
|
||||||
matches.join(", ")
|
matches.join(", ")
|
||||||
),
|
),
|
||||||
upm_core::domain::show::ShowResultError::UnsupportedQuery => {
|
aim_core::domain::show::ShowResultError::UnsupportedQuery => {
|
||||||
write!(f, "unsupported source query")
|
write!(f, "unsupported source query")
|
||||||
}
|
}
|
||||||
upm_core::domain::show::ShowResultError::InsecureHttpSource => write!(
|
aim_core::domain::show::ShowResultError::InsecureHttpSource => write!(
|
||||||
f,
|
f,
|
||||||
"insecure HTTP sources are disabled; set allow_http = true to permit them"
|
"insecure HTTP sources are disabled; set allow_http = true to permit them"
|
||||||
),
|
),
|
||||||
upm_core::domain::show::ShowResultError::NoInstallableArtifact { source } => {
|
aim_core::domain::show::ShowResultError::NoInstallableArtifact { source } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"no installable artifact found for {} {}",
|
"no installable artifact found for {} {}",
|
||||||
|
|
@ -285,18 +288,18 @@ impl std::fmt::Display for DispatchError {
|
||||||
source.locator
|
source.locator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
upm_core::domain::show::ShowResultError::AdapterResolutionFailed {
|
aim_core::domain::show::ShowResultError::AdapterResolutionFailed {
|
||||||
adapter_id,
|
adapter_id,
|
||||||
kind,
|
kind,
|
||||||
detail,
|
detail,
|
||||||
} => match kind {
|
} => match kind {
|
||||||
upm_core::domain::show::AdapterFailureKind::UnsupportedQuery => {
|
aim_core::domain::show::AdapterFailureKind::UnsupportedQuery => {
|
||||||
write!(f, "{adapter_id} does not support this query")
|
write!(f, "{adapter_id} does not support this query")
|
||||||
}
|
}
|
||||||
upm_core::domain::show::AdapterFailureKind::UnsupportedSource => {
|
aim_core::domain::show::AdapterFailureKind::UnsupportedSource => {
|
||||||
write!(f, "{adapter_id} does not support this source")
|
write!(f, "{adapter_id} does not support this source")
|
||||||
}
|
}
|
||||||
upm_core::domain::show::AdapterFailureKind::ResolutionFailed => {
|
aim_core::domain::show::AdapterFailureKind::ResolutionFailed => {
|
||||||
if let Some(detail) = detail {
|
if let Some(detail) = detail {
|
||||||
write!(f, "{adapter_id} resolution failed: {detail}")
|
write!(f, "{adapter_id} resolution failed: {detail}")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -304,27 +307,27 @@ impl std::fmt::Display for DispatchError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upm_core::domain::show::ShowResultError::GitHubDiscoveryFailed {
|
aim_core::domain::show::ShowResultError::GitHubDiscoveryFailed {
|
||||||
kind,
|
kind,
|
||||||
detail,
|
detail,
|
||||||
} => match (kind, detail) {
|
} => match (kind, detail) {
|
||||||
(
|
(
|
||||||
upm_core::domain::show::GitHubDiscoveryFailureKind::FixtureDocumentMissing,
|
aim_core::domain::show::GitHubDiscoveryFailureKind::FixtureDocumentMissing,
|
||||||
Some(detail),
|
Some(detail),
|
||||||
) => write!(f, "github discovery failed: missing fixture document {detail}"),
|
) => write!(f, "github discovery failed: missing fixture document {detail}"),
|
||||||
(
|
(
|
||||||
upm_core::domain::show::GitHubDiscoveryFailureKind::NoReleases,
|
aim_core::domain::show::GitHubDiscoveryFailureKind::NoReleases,
|
||||||
Some(detail),
|
Some(detail),
|
||||||
) => write!(f, "github discovery failed: no releases for {detail}"),
|
) => write!(f, "github discovery failed: no releases for {detail}"),
|
||||||
(upm_core::domain::show::GitHubDiscoveryFailureKind::Unsupported, _) => {
|
(aim_core::domain::show::GitHubDiscoveryFailureKind::Unsupported, _) => {
|
||||||
write!(f, "github discovery failed: unsupported source")
|
write!(f, "github discovery failed: unsupported source")
|
||||||
}
|
}
|
||||||
(upm_core::domain::show::GitHubDiscoveryFailureKind::Transport, _) => {
|
(aim_core::domain::show::GitHubDiscoveryFailureKind::Transport, _) => {
|
||||||
write!(f, "github discovery failed: transport error")
|
write!(f, "github discovery failed: transport error")
|
||||||
}
|
}
|
||||||
_ => write!(f, "github discovery failed"),
|
_ => write!(f, "github discovery failed"),
|
||||||
},
|
},
|
||||||
upm_core::domain::show::ShowResultError::NoInstallableCandidates => {
|
aim_core::domain::show::ShowResultError::NoInstallableCandidates => {
|
||||||
write!(f, "no installable candidates found")
|
write!(f, "no installable candidates found")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -334,25 +337,25 @@ impl std::fmt::Display for DispatchError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_install_error(error: &upm_core::app::add::InstallAppError) -> String {
|
fn render_install_error(error: &aim_core::app::add::InstallAppError) -> String {
|
||||||
match error {
|
match error {
|
||||||
upm_core::app::add::InstallAppError::Materialize(error) => format!("{error:?}"),
|
aim_core::app::add::InstallAppError::Materialize(error) => format!("{error:?}"),
|
||||||
upm_core::app::add::InstallAppError::Policy(error) => error.clone(),
|
aim_core::app::add::InstallAppError::Policy(error) => error.clone(),
|
||||||
upm_core::app::add::InstallAppError::Download(error) => error.to_string(),
|
aim_core::app::add::InstallAppError::Download(error) => error.to_string(),
|
||||||
upm_core::app::add::InstallAppError::DownloadIo(error) => error.to_string(),
|
aim_core::app::add::InstallAppError::DownloadIo(error) => error.to_string(),
|
||||||
upm_core::app::add::InstallAppError::HostProbe(error) => error.to_string(),
|
aim_core::app::add::InstallAppError::HostProbe(error) => error.to_string(),
|
||||||
upm_core::app::add::InstallAppError::Install(error) => error.to_string(),
|
aim_core::app::add::InstallAppError::Install(error) => error.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::add::BuildAddPlanError> for DispatchError {
|
impl From<aim_core::app::add::BuildAddPlanError> for DispatchError {
|
||||||
fn from(value: upm_core::app::add::BuildAddPlanError) -> Self {
|
fn from(value: aim_core::app::add::BuildAddPlanError) -> Self {
|
||||||
Self::AddPlan(value)
|
Self::AddPlan(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::add::InstallAppError> for DispatchError {
|
impl From<aim_core::app::add::InstallAppError> for DispatchError {
|
||||||
fn from(value: upm_core::app::add::InstallAppError) -> Self {
|
fn from(value: aim_core::app::add::InstallAppError) -> Self {
|
||||||
Self::AddInstall(value)
|
Self::AddInstall(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -363,38 +366,38 @@ impl From<ui::prompt::PromptError> for DispatchError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::update::BuildUpdatePlanError> for DispatchError {
|
impl From<aim_core::app::update::BuildUpdatePlanError> for DispatchError {
|
||||||
fn from(value: upm_core::app::update::BuildUpdatePlanError) -> Self {
|
fn from(value: aim_core::app::update::BuildUpdatePlanError) -> Self {
|
||||||
Self::UpdatePlan(value)
|
Self::UpdatePlan(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::update::ExecuteUpdatesError> for DispatchError {
|
impl From<aim_core::app::update::ExecuteUpdatesError> for DispatchError {
|
||||||
fn from(value: upm_core::app::update::ExecuteUpdatesError) -> Self {
|
fn from(value: aim_core::app::update::ExecuteUpdatesError) -> Self {
|
||||||
Self::UpdateExecution(value)
|
Self::UpdateExecution(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::remove::RemoveRegisteredAppError> for DispatchError {
|
impl From<aim_core::app::remove::RemoveRegisteredAppError> for DispatchError {
|
||||||
fn from(value: upm_core::app::remove::RemoveRegisteredAppError) -> Self {
|
fn from(value: aim_core::app::remove::RemoveRegisteredAppError) -> Self {
|
||||||
Self::RemovePlan(value)
|
Self::RemovePlan(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::registry::store::RegistryStoreError> for DispatchError {
|
impl From<aim_core::registry::store::RegistryStoreError> for DispatchError {
|
||||||
fn from(value: upm_core::registry::store::RegistryStoreError) -> Self {
|
fn from(value: aim_core::registry::store::RegistryStoreError) -> Self {
|
||||||
Self::Registry(value)
|
Self::Registry(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::app::search::SearchError> for DispatchError {
|
impl From<aim_core::app::search::SearchError> for DispatchError {
|
||||||
fn from(value: upm_core::app::search::SearchError) -> Self {
|
fn from(value: aim_core::app::search::SearchError) -> Self {
|
||||||
Self::Search(value)
|
Self::Search(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<upm_core::domain::show::ShowResultError> for DispatchError {
|
impl From<aim_core::domain::show::ShowResultError> for DispatchError {
|
||||||
fn from(value: upm_core::domain::show::ShowResultError) -> Self {
|
fn from(value: aim_core::domain::show::ShowResultError) -> Self {
|
||||||
Self::Show(value)
|
Self::Show(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -439,7 +442,7 @@ fn merge_updated_app_records(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_home(registry_path: &Path) -> PathBuf {
|
fn install_home(registry_path: &Path) -> PathBuf {
|
||||||
if env::var_os("UPM_REGISTRY_PATH").is_some() {
|
if env::var_os("AIM_REGISTRY_PATH").is_some() {
|
||||||
return registry_path
|
return registry_path
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap_or_else(|| Path::new("."))
|
.unwrap_or_else(|| Path::new("."))
|
||||||
|
|
@ -451,7 +454,7 @@ fn install_home(registry_path: &Path) -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_effective_root() -> bool {
|
fn is_effective_root() -> bool {
|
||||||
if let Some(value) = env::var_os("UPM_EFFECTIVE_ROOT") {
|
if let Some(value) = env::var_os("AIM_EFFECTIVE_ROOT") {
|
||||||
let value = value.to_string_lossy();
|
let value = value.to_string_lossy();
|
||||||
return value == "1" || value.eq_ignore_ascii_case("true");
|
return value == "1" || value.eq_ignore_ascii_case("true");
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
let loaded_theme_config = upm::cli::config::AppConfig::load();
|
let loaded_theme_config = aim_cli::cli::config::AppConfig::load();
|
||||||
upm::ui::theme::set_active_theme(upm::ui::theme::resolve_theme(
|
aim_cli::ui::theme::set_active_theme(aim_cli::ui::theme::resolve_theme(
|
||||||
&loaded_theme_config.config.theme,
|
&loaded_theme_config.config.theme,
|
||||||
));
|
));
|
||||||
for warning in loaded_theme_config.warnings {
|
for warning in loaded_theme_config.warnings {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
upm::ui::theme::warning_text(&format!("Config warning: {warning}"))
|
aim_cli::ui::theme::warning_text(&format!("Config warning: {warning}"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = match upm::config::load() {
|
let config = match aim_cli::config::load() {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("{error}");
|
eprintln!("{error}");
|
||||||
|
|
@ -18,11 +18,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cli = upm::parse();
|
let cli = aim_cli::parse();
|
||||||
let mut reporter = upm::ui::progress::TerminalProgressReporter::stderr();
|
let mut reporter = aim_cli::ui::progress::TerminalProgressReporter::stderr();
|
||||||
match upm::dispatch_with_reporter_and_config(cli, &config, &mut reporter) {
|
match aim_cli::dispatch_with_reporter_and_config(cli, &config, &mut reporter) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let output = upm::render_with_config(&result, &config);
|
let output = aim_cli::render_with_config(&result, &config);
|
||||||
if !output.is_empty() {
|
if !output.is_empty() {
|
||||||
if reporter.emitted_output() {
|
if reporter.emitted_output() {
|
||||||
println!();
|
println!();
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use aim_core::app::progress::{OperationEvent, OperationKind, OperationStage, ProgressReporter};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use upm_core::app::progress::{OperationEvent, OperationKind, OperationStage, ProgressReporter};
|
|
||||||
|
|
||||||
pub fn new_progress_bar(total: Option<u64>) -> ProgressBar {
|
pub fn new_progress_bar(total: Option<u64>) -> ProgressBar {
|
||||||
match total {
|
match total {
|
||||||
|
|
@ -241,7 +241,7 @@ impl ProgressReporter for TerminalProgressReporter {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::TerminalProgressReporter;
|
use super::TerminalProgressReporter;
|
||||||
use crate::ui::progress::{ProgressReporter, format_completed_stage_line};
|
use crate::ui::progress::{ProgressReporter, format_completed_stage_line};
|
||||||
use upm_core::app::progress::{OperationEvent, OperationStage};
|
use aim_core::app::progress::{OperationEvent, OperationStage};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stage_change_resets_byte_progress_position() {
|
fn stage_change_resets_byte_progress_position() {
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
|
|
||||||
|
use aim_core::app::add::{AddPlan, prefer_latest_tracking};
|
||||||
|
use aim_core::app::interaction::{InteractionKind, InteractionRequest};
|
||||||
use dialoguer::Select;
|
use dialoguer::Select;
|
||||||
use upm_core::app::add::{AddPlan, prefer_latest_tracking};
|
|
||||||
use upm_core::app::interaction::{InteractionKind, InteractionRequest};
|
|
||||||
|
|
||||||
const TRACKING_PREFERENCE_ENV: &str = "UPM_TRACKING_PREFERENCE";
|
const TRACKING_PREFERENCE_ENV: &str = "AIM_TRACKING_PREFERENCE";
|
||||||
|
|
||||||
pub fn render_interaction(request: &InteractionRequest) -> String {
|
pub fn render_interaction(request: &InteractionRequest) -> String {
|
||||||
match &request.kind {
|
match &request.kind {
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use console::measure_text_width;
|
use aim_core::app::add::AddPlan;
|
||||||
use upm_core::app::add::AddPlan;
|
use aim_core::domain::search::SearchResults;
|
||||||
use upm_core::domain::search::SearchResults;
|
use aim_core::domain::show::{
|
||||||
use upm_core::domain::show::{
|
|
||||||
InstalledShow, MetadataSummary, RemoteInteractionSummary, RemoteShow, ShowResult, SourceSummary,
|
InstalledShow, MetadataSummary, RemoteInteractionSummary, RemoteShow, ShowResult, SourceSummary,
|
||||||
};
|
};
|
||||||
use upm_core::domain::update::UpdateExecutionStatus;
|
use aim_core::domain::update::UpdateExecutionStatus;
|
||||||
|
use console::measure_text_width;
|
||||||
|
|
||||||
use crate::DispatchResult;
|
use crate::DispatchResult;
|
||||||
use crate::config::CliConfig;
|
use crate::config::CliConfig;
|
||||||
|
|
@ -38,10 +38,10 @@ pub fn render_dispatch_result_with_config(result: &DispatchResult, config: &CliC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_added_app(added: &upm_core::app::add::InstalledApp) -> String {
|
fn render_added_app(added: &aim_core::app::add::InstalledApp) -> String {
|
||||||
let scope = match added.install_scope {
|
let scope = match added.install_scope {
|
||||||
upm_core::domain::app::InstallScope::User => "user",
|
aim_core::domain::app::InstallScope::User => "user",
|
||||||
upm_core::domain::app::InstallScope::System => "system",
|
aim_core::domain::app::InstallScope::System => "system",
|
||||||
};
|
};
|
||||||
|
|
||||||
let warning_lines = added
|
let warning_lines = added
|
||||||
|
|
@ -104,7 +104,7 @@ fn render_pending_add(plan: &AddPlan) -> String {
|
||||||
.join("\n")
|
.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_list(rows: &[upm_core::app::list::ListRow]) -> String {
|
fn render_list(rows: &[aim_core::app::list::ListRow]) -> String {
|
||||||
if rows.is_empty() {
|
if rows.is_empty() {
|
||||||
return crate::ui::theme::muted("No installed apps yet");
|
return crate::ui::theme::muted("No installed apps yet");
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ fn format_list_row(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_removed_app(removed: &upm_core::app::remove::RemovalResult) -> String {
|
fn render_removed_app(removed: &aim_core::app::remove::RemovalResult) -> String {
|
||||||
let warning_lines = removed
|
let warning_lines = removed
|
||||||
.warnings
|
.warnings
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -385,10 +385,10 @@ fn metadata_detail_lines(metadata: &MetadataSummary) -> Vec<String> {
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
fn installed_files_header(scope: Option<upm_core::domain::app::InstallScope>) -> String {
|
fn installed_files_header(scope: Option<aim_core::domain::app::InstallScope>) -> String {
|
||||||
let label = match scope {
|
let label = match scope {
|
||||||
Some(upm_core::domain::app::InstallScope::User) => "Installed as User",
|
Some(aim_core::domain::app::InstallScope::User) => "Installed as User",
|
||||||
Some(upm_core::domain::app::InstallScope::System) => "Installed as System",
|
Some(aim_core::domain::app::InstallScope::System) => "Installed as System",
|
||||||
None => "Installed files",
|
None => "Installed files",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -422,11 +422,11 @@ fn truncate_checksum(checksum: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata_kind_label(kind: upm_core::domain::update::ParsedMetadataKind) -> &'static str {
|
fn metadata_kind_label(kind: aim_core::domain::update::ParsedMetadataKind) -> &'static str {
|
||||||
match kind {
|
match kind {
|
||||||
upm_core::domain::update::ParsedMetadataKind::Unknown => "unknown",
|
aim_core::domain::update::ParsedMetadataKind::Unknown => "unknown",
|
||||||
upm_core::domain::update::ParsedMetadataKind::ElectronBuilder => "electron-builder",
|
aim_core::domain::update::ParsedMetadataKind::ElectronBuilder => "electron-builder",
|
||||||
upm_core::domain::update::ParsedMetadataKind::Zsync => "zsync",
|
aim_core::domain::update::ParsedMetadataKind::Zsync => "zsync",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -509,7 +509,7 @@ fn render_remote_show(remote: &RemoteShow) -> String {
|
||||||
lines.join("\n")
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_file_paths(added: &upm_core::app::add::InstalledApp) -> Vec<String> {
|
fn install_file_paths(added: &aim_core::app::add::InstalledApp) -> Vec<String> {
|
||||||
[
|
[
|
||||||
Some(
|
Some(
|
||||||
added
|
added
|
||||||
|
|
@ -595,7 +595,7 @@ fn render_search_results_with_config(results: &SearchResults, config: &CliConfig
|
||||||
render_search_results(results)
|
render_search_results(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_updated_apps(result: &upm_core::domain::update::UpdateExecutionResult) -> String {
|
fn render_updated_apps(result: &aim_core::domain::update::UpdateExecutionResult) -> String {
|
||||||
let mut lines = vec![
|
let mut lines = vec![
|
||||||
crate::ui::theme::heading("Update Summary"),
|
crate::ui::theme::heading("Update Summary"),
|
||||||
format!("updated apps: {}", result.updated_count()),
|
format!("updated apps: {}", result.updated_count()),
|
||||||
|
|
@ -621,7 +621,7 @@ fn render_updated_apps(result: &upm_core::domain::update::UpdateExecutionResult)
|
||||||
lines.join("\n")
|
lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_update_plan(plan: &upm_core::domain::update::UpdatePlan) -> String {
|
fn render_update_plan(plan: &aim_core::domain::update::UpdatePlan) -> String {
|
||||||
let mut lines = vec![render_update_summary(plan.items.len(), plan.items.len(), 0)];
|
let mut lines = vec![render_update_summary(plan.items.len(), plan.items.len(), 0)];
|
||||||
|
|
||||||
for item in &plan.items {
|
for item in &plan.items {
|
||||||
|
|
@ -2,6 +2,7 @@ use std::collections::BTreeSet;
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use aim_core::domain::search::{SearchInstallStatus, SearchResult, SearchResults};
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
use crossterm::terminal::{
|
use crossterm::terminal::{
|
||||||
|
|
@ -13,7 +14,6 @@ use ratatui::style::Modifier;
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::{Clear, List, ListItem, Paragraph, Wrap};
|
use ratatui::widgets::{Clear, List, ListItem, Paragraph, Wrap};
|
||||||
use ratatui::{Frame, Terminal};
|
use ratatui::{Frame, Terminal};
|
||||||
use upm_core::domain::search::{SearchInstallStatus, SearchResult, SearchResults};
|
|
||||||
|
|
||||||
use crate::config::{CliConfig, SearchConfig};
|
use crate::config::{CliConfig, SearchConfig};
|
||||||
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use predicates::str::contains;
|
use predicates::str::contains;
|
||||||
|
|
||||||
|
use aim_cli::cli::args::Command as AimCommand;
|
||||||
|
use aim_cli::{Cli, DispatchError};
|
||||||
|
use aim_core::domain::show::{ShowResultError, SourceSummary};
|
||||||
|
use aim_core::domain::source::SourceKind;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use upm::cli::args::Command as UpmCommand;
|
|
||||||
use upm::{Cli, DispatchError};
|
|
||||||
use upm_core::domain::show::{ShowResultError, SourceSummary};
|
|
||||||
use upm_core::domain::source::SourceKind;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn help_lists_expected_commands() {
|
fn help_lists_expected_commands() {
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
cmd.arg("--help")
|
cmd.arg("--help")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -22,20 +22,20 @@ fn help_lists_expected_commands() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_parses_show_subcommand() {
|
fn cli_parses_show_subcommand() {
|
||||||
let cli = Cli::try_parse_from(["upm", "show", "legacy-bat"]).unwrap();
|
let cli = Cli::try_parse_from(["aim", "show", "legacy-bat"]).unwrap();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(UpmCommand::Show { value }) => assert_eq!(value.as_deref(), Some("legacy-bat")),
|
Some(AimCommand::Show { value }) => assert_eq!(value.as_deref(), Some("legacy-bat")),
|
||||||
other => panic!("expected show command, got {other:?}"),
|
other => panic!("expected show command, got {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_parses_bare_show_subcommand() {
|
fn cli_parses_bare_show_subcommand() {
|
||||||
let cli = Cli::try_parse_from(["upm", "show"]).unwrap();
|
let cli = Cli::try_parse_from(["aim", "show"]).unwrap();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(UpmCommand::Show { value }) => assert_eq!(value, None),
|
Some(AimCommand::Show { value }) => assert_eq!(value, None),
|
||||||
other => panic!("expected bare show command, got {other:?}"),
|
other => panic!("expected bare show command, got {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,6 @@ use assert_cmd::Command;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_shows_help() {
|
fn cli_shows_help() {
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
cmd.arg("--help").assert().success();
|
cmd.arg("--help").assert().success();
|
||||||
}
|
}
|
||||||
66
crates/aim-cli/tests/config_loading.rs
Normal file
66
crates/aim-cli/tests/config_loading.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
use aim_cli::config::{CliConfig, ConfigError, SearchConfig, load_from_path};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_config_file_returns_defaults() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let path = dir.path().join("config.toml");
|
||||||
|
|
||||||
|
let config = load_from_path(&path).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config, CliConfig::default());
|
||||||
|
assert_eq!(config.search, SearchConfig::default());
|
||||||
|
assert!(!config.allow_http);
|
||||||
|
assert!(config.search.bottom_to_top);
|
||||||
|
assert!(!config.search.skip_confirmation);
|
||||||
|
assert_eq!(config.theme.accent, "#b388ff");
|
||||||
|
assert_eq!(config.theme.accent_secondary, "#d5c2ff");
|
||||||
|
assert_eq!(config.theme.dim, "#7f7396");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_section_overrides_defaults() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let path = dir.path().join("config.toml");
|
||||||
|
std::fs::write(
|
||||||
|
&path,
|
||||||
|
"allow_http = true\n\n[search]\nbottom_to_top = false\nskip_confirmation = true\n\n[theme]\naccent = \"#9f6bff\"\naccent_secondary = \"#efe7ff\"\ndim = \"#6b6480\"\n",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let config = load_from_path(&path).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
CliConfig {
|
||||||
|
allow_http: true,
|
||||||
|
search: SearchConfig {
|
||||||
|
bottom_to_top: false,
|
||||||
|
skip_confirmation: true,
|
||||||
|
},
|
||||||
|
theme: aim_cli::config::ThemeConfig {
|
||||||
|
accent: "#9f6bff".to_owned(),
|
||||||
|
accent_secondary: "#efe7ff".to_owned(),
|
||||||
|
dim: "#6b6480".to_owned(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_toml_returns_path_aware_error() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
let path = dir.path().join("config.toml");
|
||||||
|
std::fs::write(&path, "[search\nskip_confirmation = true\n").unwrap();
|
||||||
|
|
||||||
|
let error = load_from_path(&path).unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
ConfigError::Parse {
|
||||||
|
path: error_path, ..
|
||||||
|
} => {
|
||||||
|
assert_eq!(error_path, path);
|
||||||
|
}
|
||||||
|
other => panic!("expected parse error, got {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
|
use aim_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
||||||
|
use aim_core::registry::model::Registry;
|
||||||
|
use aim_core::registry::store::RegistryStore;
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use predicates::prelude::PredicateBooleanExt;
|
use predicates::prelude::PredicateBooleanExt;
|
||||||
use predicates::str::contains;
|
use predicates::str::contains;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use upm_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
|
||||||
use upm_core::registry::model::Registry;
|
|
||||||
use upm_core::registry::store::RegistryStore;
|
|
||||||
|
|
||||||
const FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";
|
const FIXTURE_MODE_ENV: &str = "AIM_GITHUB_FIXTURE_MODE";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_command_runs_without_registry_entries() {
|
fn list_command_runs_without_registry_entries() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("list")
|
cmd.arg("list")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("No installed apps yet"));
|
.stdout(contains("No installed apps yet"));
|
||||||
|
|
@ -31,10 +31,10 @@ fn list_command_reads_registered_apps_from_registry_file() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("list")
|
cmd.arg("list")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("Name"))
|
.stdout(contains("Name"))
|
||||||
|
|
@ -54,10 +54,10 @@ fn remove_command_removes_registered_app_from_registry_file() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["remove", "bat"])
|
cmd.args(["remove", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("Removed Bat"))
|
.stdout(contains("Removed Bat"))
|
||||||
|
|
@ -73,14 +73,14 @@ fn remove_command_uninstalls_managed_files() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let install_home = dir.path().join("install-home");
|
let install_home = dir.path().join("install-home");
|
||||||
let payload_path = install_home.join(".local/lib/upm/appimages/sharkdp-bat.AppImage");
|
let payload_path = install_home.join(".local/lib/aim/appimages/sharkdp-bat.AppImage");
|
||||||
let desktop_path = install_home.join(".local/share/applications/upm-sharkdp-bat.desktop");
|
let desktop_path = install_home.join(".local/share/applications/aim-sharkdp-bat.desktop");
|
||||||
let icon_path = install_home.join(".local/share/icons/hicolor/256x256/apps/sharkdp-bat.png");
|
let icon_path = install_home.join(".local/share/icons/hicolor/256x256/apps/sharkdp-bat.png");
|
||||||
|
|
||||||
let mut add_cmd = Command::cargo_bin("upm").unwrap();
|
let mut add_cmd = Command::cargo_bin("aim").unwrap();
|
||||||
add_cmd
|
add_cmd
|
||||||
.arg("sharkdp/bat")
|
.arg("sharkdp/bat")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
@ -89,10 +89,10 @@ fn remove_command_uninstalls_managed_files() {
|
||||||
assert!(desktop_path.exists());
|
assert!(desktop_path.exists());
|
||||||
assert!(icon_path.exists());
|
assert!(icon_path.exists());
|
||||||
|
|
||||||
let mut remove_cmd = Command::cargo_bin("upm").unwrap();
|
let mut remove_cmd = Command::cargo_bin("aim").unwrap();
|
||||||
remove_cmd
|
remove_cmd
|
||||||
.args(["remove", "sharkdp-bat"])
|
.args(["remove", "sharkdp-bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("\nRemoved bat"))
|
.stdout(contains("\nRemoved bat"))
|
||||||
|
|
@ -101,7 +101,7 @@ fn remove_command_uninstalls_managed_files() {
|
||||||
.stdout(contains("Removed app:").not())
|
.stdout(contains("Removed app:").not())
|
||||||
.stdout(contains("Removed files"))
|
.stdout(contains("Removed files"))
|
||||||
.stdout(contains("sharkdp-bat.AppImage"))
|
.stdout(contains("sharkdp-bat.AppImage"))
|
||||||
.stdout(contains("upm-sharkdp-bat.desktop"))
|
.stdout(contains("aim-sharkdp-bat.desktop"))
|
||||||
.stdout(contains("sharkdp-bat.png"));
|
.stdout(contains("sharkdp-bat.png"));
|
||||||
|
|
||||||
assert!(!payload_path.exists());
|
assert!(!payload_path.exists());
|
||||||
|
|
@ -113,10 +113,10 @@ fn remove_command_uninstalls_managed_files() {
|
||||||
fn query_command_registers_unambiguous_app_in_registry_file() {
|
fn query_command_registers_unambiguous_app_in_registry_file() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("sharkdp/bat")
|
cmd.arg("sharkdp/bat")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -140,10 +140,10 @@ fn query_command_registers_unambiguous_app_in_registry_file() {
|
||||||
fn old_release_query_renders_tracking_prompt_without_writing_registry() {
|
fn old_release_query_renders_tracking_prompt_without_writing_registry() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage")
|
cmd.arg("https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -158,12 +158,12 @@ fn old_release_query_renders_tracking_prompt_without_writing_registry() {
|
||||||
fn old_release_query_can_track_latest_and_register_app() {
|
fn old_release_query_can_track_latest_and_register_app() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage")
|
cmd.arg("https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.env("UPM_TRACKING_PREFERENCE", "latest")
|
.env("AIM_TRACKING_PREFERENCE", "latest")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("\nInstalled t3code (user)"))
|
.stdout(contains("\nInstalled t3code (user)"))
|
||||||
|
|
@ -182,34 +182,14 @@ fn old_release_query_can_track_latest_and_register_app() {
|
||||||
assert!(contents.contains("locator = \"pingdotgg/t3code\""));
|
assert!(contents.contains("locator = \"pingdotgg/t3code\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn old_release_query_ignores_legacy_tracking_preference_env() {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
let registry_path = dir.path().join("registry.toml");
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
|
||||||
|
|
||||||
cmd.arg("https://github.com/pingdotgg/t3code/releases/download/v0.0.11/T3-Code-0.0.11-x86_64.AppImage")
|
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
|
||||||
.env("AIM_TRACKING_PREFERENCE", "latest")
|
|
||||||
.assert()
|
|
||||||
.success()
|
|
||||||
.stdout(contains("Choose update tracking"))
|
|
||||||
.stdout(contains("v0.0.11"))
|
|
||||||
.stdout(contains("v0.0.12"))
|
|
||||||
.stdout(contains("Installed t3code").not());
|
|
||||||
|
|
||||||
assert!(!registry_path.exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_add_installs_and_renders_resolved_mode() {
|
fn cli_add_installs_and_renders_resolved_mode() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("sharkdp/bat")
|
cmd.arg("sharkdp/bat")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -224,10 +204,10 @@ fn cli_add_installs_and_renders_resolved_mode() {
|
||||||
fn positional_query_falls_back_to_search_for_plain_name_queries() {
|
fn positional_query_falls_back_to_search_for_plain_name_queries() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("firefox")
|
cmd.arg("firefox")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -246,10 +226,10 @@ fn positional_query_falls_back_to_search_for_plain_name_queries() {
|
||||||
fn positional_query_falls_back_to_empty_search_when_direct_item_has_no_appimage() {
|
fn positional_query_falls_back_to_empty_search_when_direct_item_has_no_appimage() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("appimagehub/2337998")
|
cmd.arg("appimagehub/2337998")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -266,10 +246,10 @@ fn positional_query_falls_back_to_empty_search_when_direct_item_has_no_appimage(
|
||||||
fn cli_add_installs_appimagehub_source_with_truthful_origin() {
|
fn cli_add_installs_appimagehub_source_with_truthful_origin() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("appimagehub/2338455")
|
cmd.arg("appimagehub/2338455")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -293,10 +273,10 @@ fn cli_add_installs_appimagehub_source_with_truthful_origin() {
|
||||||
fn cli_add_installs_gitlab_source_with_truthful_origin() {
|
fn cli_add_installs_gitlab_source_with_truthful_origin() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("https://gitlab.com/example/team-app")
|
cmd.arg("https://gitlab.com/example/team-app")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -318,10 +298,10 @@ fn cli_add_preserves_direct_url_origin_for_provider_like_downloads() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let query = "https://sourceforge.net/projects/team-app/files/team-app-1.0.0.AppImage/download";
|
let query = "https://sourceforge.net/projects/team-app/files/team-app-1.0.0.AppImage/download";
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg(query)
|
cmd.arg(query)
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -341,10 +321,10 @@ fn cli_add_installs_sourceforge_latest_download_with_truthful_origin() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let query = "https://sourceforge.net/projects/team-app/files/latest/download";
|
let query = "https://sourceforge.net/projects/team-app/files/latest/download";
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg(query)
|
cmd.arg(query)
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -363,10 +343,10 @@ fn cli_add_installs_sourceforge_latest_download_with_truthful_origin() {
|
||||||
fn cli_rejects_insecure_http_direct_urls_by_default() {
|
fn cli_rejects_insecure_http_direct_urls_by_default() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("http://example.com/team-app.AppImage")
|
cmd.arg("http://example.com/team-app.AppImage")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(contains("insecure HTTP sources are disabled"));
|
.stderr(contains("insecure HTTP sources are disabled"));
|
||||||
|
|
@ -380,11 +360,11 @@ fn cli_allows_insecure_http_direct_urls_when_config_enables_it() {
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let config_path = dir.path().join("config.toml");
|
let config_path = dir.path().join("config.toml");
|
||||||
std::fs::write(&config_path, "allow_http = true\n").unwrap();
|
std::fs::write(&config_path, "allow_http = true\n").unwrap();
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("http://example.com/team-app.AppImage")
|
cmd.arg("http://example.com/team-app.AppImage")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env("UPM_CONFIG_PATH", &config_path)
|
.env("AIM_CONFIG_PATH", &config_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -400,13 +380,13 @@ fn cli_rejects_insecure_appimagehub_download_urls_even_when_http_is_allowed() {
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let config_path = dir.path().join("config.toml");
|
let config_path = dir.path().join("config.toml");
|
||||||
std::fs::write(&config_path, "allow_http = true\n").unwrap();
|
std::fs::write(&config_path, "allow_http = true\n").unwrap();
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("appimagehub/2338455")
|
cmd.arg("appimagehub/2338455")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env("UPM_CONFIG_PATH", &config_path)
|
.env("AIM_CONFIG_PATH", &config_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.env("UPM_APPIMAGEHUB_FIXTURE_INSECURE_HTTP", "1")
|
.env("AIM_APPIMAGEHUB_FIXTURE_INSECURE_HTTP", "1")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(contains("insecure appimagehub download url"));
|
.stderr(contains("insecure appimagehub download url"));
|
||||||
|
|
@ -416,12 +396,12 @@ fn cli_rejects_insecure_appimagehub_download_urls_even_when_http_is_allowed() {
|
||||||
fn cli_rejects_appimagehub_install_when_md5_does_not_match() {
|
fn cli_rejects_appimagehub_install_when_md5_does_not_match() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("appimagehub/2338455")
|
cmd.arg("appimagehub/2338455")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.env("UPM_APPIMAGEHUB_FIXTURE_BAD_MD5", "1")
|
.env("AIM_APPIMAGEHUB_FIXTURE_BAD_MD5", "1")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
.stderr(contains("weak provider checksum did not match"));
|
.stderr(contains("weak provider checksum did not match"));
|
||||||
|
|
@ -432,10 +412,10 @@ fn cli_add_installs_sourceforge_release_folder_with_truthful_origin() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let query = "https://sourceforge.net/projects/team-app/files/releases/beta/download";
|
let query = "https://sourceforge.net/projects/team-app/files/releases/beta/download";
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg(query)
|
cmd.arg(query)
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -456,10 +436,10 @@ fn cli_add_file_like_sourceforge_release_download_stores_releases_root_and_prese
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let query =
|
let query =
|
||||||
"https://sourceforge.net/projects/team-app/files/releases/team-app-1.0.0.AppImage/download";
|
"https://sourceforge.net/projects/team-app/files/releases/team-app-1.0.0.AppImage/download";
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg(query)
|
cmd.arg(query)
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -482,10 +462,10 @@ fn cli_add_file_like_sourceforge_release_download_stores_releases_root_and_prese
|
||||||
fn cli_reports_unsupported_source_queries_distinctly() {
|
fn cli_reports_unsupported_source_queries_distinctly() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("https://gitlab.com/example")
|
cmd.arg("https://gitlab.com/example")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -499,10 +479,10 @@ fn cli_reports_unsupported_source_queries_distinctly() {
|
||||||
fn cli_reports_supported_sources_without_installable_artifacts_distinctly() {
|
fn cli_reports_supported_sources_without_installable_artifacts_distinctly() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("https://sourceforge.net/projects/team-app/")
|
cmd.arg("https://sourceforge.net/projects/team-app/")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -516,10 +496,10 @@ fn cli_reports_supported_sources_without_installable_artifacts_distinctly() {
|
||||||
fn cli_add_emits_live_progress_to_stderr() {
|
fn cli_add_emits_live_progress_to_stderr() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("sharkdp/bat")
|
cmd.arg("sharkdp/bat")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -537,7 +517,7 @@ fn cli_add_emits_live_progress_to_stderr() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bare_upm_review_renders_review_heading() {
|
fn bare_aim_review_renders_review_heading() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let store = RegistryStore::new(registry_path.clone());
|
let store = RegistryStore::new(registry_path.clone());
|
||||||
|
|
@ -562,9 +542,9 @@ fn bare_upm_review_renders_review_heading() {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.env("UPM_REGISTRY_PATH", ®istry_path)
|
cmd.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout(contains("Update Review"))
|
.stdout(contains("Update Review"))
|
||||||
|
|
@ -581,10 +561,10 @@ fn remove_command_emits_live_progress_to_stderr() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["remove", "bat"])
|
cmd.args(["remove", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stderr(contains("Removing bat"))
|
.stderr(contains("Removing bat"))
|
||||||
|
|
@ -599,11 +579,11 @@ fn system_request_on_immutable_host_falls_back_to_user_install() {
|
||||||
let os_release_path = dir.path().join("os-release");
|
let os_release_path = dir.path().join("os-release");
|
||||||
std::fs::write(&os_release_path, "ID=fedora\nVARIANT_ID=silverblue\n").unwrap();
|
std::fs::write(&os_release_path, "ID=fedora\nVARIANT_ID=silverblue\n").unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["--system", "sharkdp/bat"])
|
cmd.args(["--system", "sharkdp/bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env("UPM_OS_RELEASE_PATH", &os_release_path)
|
.env("AIM_OS_RELEASE_PATH", &os_release_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -617,7 +597,7 @@ fn update_command_applies_updates() {
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let payload_path = dir
|
let payload_path = dir
|
||||||
.path()
|
.path()
|
||||||
.join("install-home/.local/lib/upm/appimages/pingdotgg-t3code.AppImage");
|
.join("install-home/.local/lib/aim/appimages/pingdotgg-t3code.AppImage");
|
||||||
let store = RegistryStore::new(registry_path.clone());
|
let store = RegistryStore::new(registry_path.clone());
|
||||||
store
|
store
|
||||||
.save(&Registry {
|
.save(&Registry {
|
||||||
|
|
@ -640,10 +620,10 @@ fn update_command_applies_updates() {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("update")
|
cmd.arg("update")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -683,10 +663,10 @@ fn update_command_emits_live_progress_to_stderr() {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("update")
|
cmd.arg("update")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -702,7 +682,7 @@ fn update_command_reports_when_previous_installation_is_restored() {
|
||||||
let install_home = dir.path().join("install-home");
|
let install_home = dir.path().join("install-home");
|
||||||
let store = RegistryStore::new(registry_path.clone());
|
let store = RegistryStore::new(registry_path.clone());
|
||||||
let stable_id = "url-example.com-downloads-team-app.appimage";
|
let stable_id = "url-example.com-downloads-team-app.appimage";
|
||||||
let payload_path = install_home.join(format!(".local/lib/upm/appimages/{stable_id}.AppImage"));
|
let payload_path = install_home.join(format!(".local/lib/aim/appimages/{stable_id}.AppImage"));
|
||||||
|
|
||||||
std::fs::create_dir_all(payload_path.parent().unwrap()).unwrap();
|
std::fs::create_dir_all(payload_path.parent().unwrap()).unwrap();
|
||||||
std::fs::write(&payload_path, b"previous-payload").unwrap();
|
std::fs::write(&payload_path, b"previous-payload").unwrap();
|
||||||
|
|
@ -716,11 +696,11 @@ fn update_command_reports_when_previous_installation_is_restored() {
|
||||||
stable_id: stable_id.to_owned(),
|
stable_id: stable_id.to_owned(),
|
||||||
display_name: "https://example.com/downloads/team-app.AppImage".to_owned(),
|
display_name: "https://example.com/downloads/team-app.AppImage".to_owned(),
|
||||||
source_input: Some("https://example.com/downloads/team-app.AppImage".to_owned()),
|
source_input: Some("https://example.com/downloads/team-app.AppImage".to_owned()),
|
||||||
source: Some(upm_core::domain::source::SourceRef {
|
source: Some(aim_core::domain::source::SourceRef {
|
||||||
kind: upm_core::domain::source::SourceKind::DirectUrl,
|
kind: aim_core::domain::source::SourceKind::DirectUrl,
|
||||||
locator: "https://example.com/downloads/team-app.AppImage".to_owned(),
|
locator: "https://example.com/downloads/team-app.AppImage".to_owned(),
|
||||||
input_kind: upm_core::domain::source::SourceInputKind::DirectUrl,
|
input_kind: aim_core::domain::source::SourceInputKind::DirectUrl,
|
||||||
normalized_kind: upm_core::domain::source::NormalizedSourceKind::DirectUrl,
|
normalized_kind: aim_core::domain::source::NormalizedSourceKind::DirectUrl,
|
||||||
canonical_locator: None,
|
canonical_locator: None,
|
||||||
requested_tag: None,
|
requested_tag: None,
|
||||||
requested_asset_name: None,
|
requested_asset_name: None,
|
||||||
|
|
@ -739,10 +719,10 @@ fn update_command_reports_when_previous_installation_is_restored() {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.arg("update")
|
cmd.arg("update")
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.env("DISPLAY", ":99")
|
.env("DISPLAY", ":99")
|
||||||
.env("XDG_CURRENT_DESKTOP", "test")
|
.env("XDG_CURRENT_DESKTOP", "test")
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use upm::config::SearchConfig;
|
use aim_cli::config::SearchConfig;
|
||||||
use upm::ui::search_browser::{BrowserPhase, SearchBrowserState, SubmitAction};
|
use aim_cli::ui::search_browser::{BrowserPhase, SearchBrowserState, SubmitAction};
|
||||||
use upm_core::domain::search::{SearchInstallStatus, SearchResult};
|
use aim_core::domain::search::{SearchInstallStatus, SearchResult};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn browser_defaults_to_bottom_to_top_ordering() {
|
fn browser_defaults_to_bottom_to_top_ordering() {
|
||||||
|
|
@ -81,7 +81,7 @@ fn invalid_numeric_input_keeps_last_good_selection_visible() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn highlight_segments_marks_matching_query_fragments() {
|
fn highlight_segments_marks_matching_query_fragments() {
|
||||||
let fragments = upm::ui::search_browser::highlight_segments("pingdotgg/t3code", "dotgg");
|
let fragments = aim_cli::ui::search_browser::highlight_segments("pingdotgg/t3code", "dotgg");
|
||||||
|
|
||||||
assert_eq!(fragments.len(), 3);
|
assert_eq!(fragments.len(), 3);
|
||||||
assert_eq!(fragments[1].text, "dotgg");
|
assert_eq!(fragments[1].text, "dotgg");
|
||||||
|
|
@ -123,8 +123,8 @@ fn submit_selection_can_skip_confirmation_from_config() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
action,
|
action,
|
||||||
SubmitAction::Confirmed(upm::ui::search_browser::SearchSelection {
|
SubmitAction::Confirmed(aim_cli::ui::search_browser::SearchSelection {
|
||||||
rows: vec![upm::ui::search_browser::SearchRow {
|
rows: vec![aim_cli::ui::search_browser::SearchRow {
|
||||||
status: SearchInstallStatus::Available,
|
status: SearchInstallStatus::Available,
|
||||||
provider_id: "github".to_owned(),
|
provider_id: "github".to_owned(),
|
||||||
display_name: "charlie/app".to_owned(),
|
display_name: "charlie/app".to_owned(),
|
||||||
|
|
@ -3,16 +3,16 @@ use predicates::prelude::PredicateBooleanExt;
|
||||||
use predicates::str::contains;
|
use predicates::str::contains;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
const FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";
|
const FIXTURE_MODE_ENV: &str = "AIM_GITHUB_FIXTURE_MODE";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search_command_renders_remote_github_results() {
|
fn search_command_renders_remote_github_results() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -46,10 +46,10 @@ fn search_command_renders_local_matches_in_deterministic_order() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -69,10 +69,10 @@ fn search_command_is_read_only_for_registry_contents() {
|
||||||
let original = "version = 1\n[[apps]]\nstable_id = \"bat\"\ndisplay_name = \"Bat\"\n";
|
let original = "version = 1\n[[apps]]\nstable_id = \"bat\"\ndisplay_name = \"Bat\"\n";
|
||||||
std::fs::write(®istry_path, original).unwrap();
|
std::fs::write(®istry_path, original).unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
@ -88,11 +88,11 @@ fn search_command_fails_fast_on_malformed_config() {
|
||||||
let config_path = dir.path().join("config.toml");
|
let config_path = dir.path().join("config.toml");
|
||||||
std::fs::write(&config_path, "[search\nskip_confirmation = true\n").unwrap();
|
std::fs::write(&config_path, "[search\nskip_confirmation = true\n").unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env("UPM_CONFIG_PATH", &config_path)
|
.env("AIM_CONFIG_PATH", &config_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.failure()
|
.failure()
|
||||||
|
|
@ -110,11 +110,11 @@ fn search_command_uses_plain_text_output_when_not_on_a_tty() {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env("UPM_CONFIG_PATH", &config_path)
|
.env("AIM_CONFIG_PATH", &config_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -127,10 +127,10 @@ fn search_command_uses_plain_text_output_when_not_on_a_tty() {
|
||||||
fn search_command_reports_loading_status_to_stderr() {
|
fn search_command_reports_loading_status_to_stderr() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "bat"])
|
cmd.args(["search", "bat"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -141,10 +141,10 @@ fn search_command_reports_loading_status_to_stderr() {
|
||||||
fn search_command_keeps_empty_results_in_plain_text_mode() {
|
fn search_command_keeps_empty_results_in_plain_text_mode() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "no-such-app-image-query"])
|
cmd.args(["search", "no-such-app-image-query"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -156,10 +156,10 @@ fn search_command_keeps_empty_results_in_plain_text_mode() {
|
||||||
fn search_command_renders_appimagehub_results() {
|
fn search_command_renders_appimagehub_results() {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let registry_path = dir.path().join("registry.toml");
|
let registry_path = dir.path().join("registry.toml");
|
||||||
let mut cmd = Command::cargo_bin("upm").unwrap();
|
let mut cmd = Command::cargo_bin("aim").unwrap();
|
||||||
|
|
||||||
cmd.args(["search", "firefox"])
|
cmd.args(["search", "firefox"])
|
||||||
.env("UPM_REGISTRY_PATH", ®istry_path)
|
.env("AIM_REGISTRY_PATH", ®istry_path)
|
||||||
.env(FIXTURE_MODE_ENV, "1")
|
.env(FIXTURE_MODE_ENV, "1")
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
use upm::DispatchResult;
|
use aim_cli::DispatchResult;
|
||||||
use upm::ui::prompt::render_interaction;
|
use aim_cli::ui::prompt::render_interaction;
|
||||||
use upm::ui::render::{render_dispatch_result, render_update_summary};
|
use aim_cli::ui::render::{render_dispatch_result, render_update_summary};
|
||||||
use upm::ui::search_browser::{SearchRow, format_search_row, render_confirmation_summary};
|
use aim_cli::ui::search_browser::{SearchRow, format_search_row, render_confirmation_summary};
|
||||||
use upm_core::app::add::InstalledApp;
|
use aim_core::app::add::InstalledApp;
|
||||||
use upm_core::app::interaction::{InteractionKind, InteractionRequest};
|
use aim_core::app::interaction::{InteractionKind, InteractionRequest};
|
||||||
use upm_core::app::list::ListRow;
|
use aim_core::app::list::ListRow;
|
||||||
use upm_core::app::remove::{RemovalPlan, RemovalResult};
|
use aim_core::app::remove::{RemovalPlan, RemovalResult};
|
||||||
use upm_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
use aim_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
||||||
use upm_core::domain::search::SearchInstallStatus;
|
use aim_core::domain::search::SearchInstallStatus;
|
||||||
use upm_core::domain::show::{
|
use aim_core::domain::show::{
|
||||||
InstalledShow, MetadataSummary, RemoteArtifactSummary, RemoteShow, ShowResult, SourceSummary,
|
InstalledShow, MetadataSummary, RemoteArtifactSummary, RemoteShow, ShowResult, SourceSummary,
|
||||||
TrackedInstallPaths, UpdateChannelSummary, UpdateStrategySummary,
|
TrackedInstallPaths, UpdateChannelSummary, UpdateStrategySummary,
|
||||||
};
|
};
|
||||||
use upm_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind, SourceRef};
|
use aim_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind, SourceRef};
|
||||||
use upm_core::domain::update::ArtifactCandidate;
|
use aim_core::domain::update::ArtifactCandidate;
|
||||||
use upm_core::domain::update::{
|
use aim_core::domain::update::{
|
||||||
ChannelPreference, ParsedMetadataKind, PlannedUpdate, UpdateChannelKind, UpdatePlan,
|
ChannelPreference, ParsedMetadataKind, PlannedUpdate, UpdateChannelKind, UpdatePlan,
|
||||||
};
|
};
|
||||||
use upm_core::integration::install::InstallOutcome;
|
use aim_core::integration::install::InstallOutcome;
|
||||||
|
|
||||||
fn muted_bold_label(title: &str) -> String {
|
fn muted_bold_label(title: &str) -> String {
|
||||||
let mut style = upm::ui::theme::current_theme().muted;
|
let mut style = aim_cli::ui::theme::current_theme().muted;
|
||||||
style.bold = true;
|
style.bold = true;
|
||||||
upm::ui::theme::apply_style_spec(&format!("{title}:"), &style)
|
aim_cli::ui::theme::apply_style_spec(&format!("{title}:"), &style)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -87,13 +87,13 @@ fn removal_summary_lists_removed_files() {
|
||||||
stable_id: "bat".to_owned(),
|
stable_id: "bat".to_owned(),
|
||||||
display_name: "Bat".to_owned(),
|
display_name: "Bat".to_owned(),
|
||||||
artifact_paths: vec![
|
artifact_paths: vec![
|
||||||
"/tmp/install-home/.local/lib/upm/appimages/bat.AppImage".to_owned(),
|
"/tmp/install-home/.local/lib/aim/appimages/bat.AppImage".to_owned(),
|
||||||
"/tmp/install-home/.local/share/applications/upm-bat.desktop".to_owned(),
|
"/tmp/install-home/.local/share/applications/aim-bat.desktop".to_owned(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
removed_paths: vec![
|
removed_paths: vec![
|
||||||
"/tmp/install-home/.local/lib/upm/appimages/bat.AppImage".to_owned(),
|
"/tmp/install-home/.local/lib/aim/appimages/bat.AppImage".to_owned(),
|
||||||
"/tmp/install-home/.local/share/applications/upm-bat.desktop".to_owned(),
|
"/tmp/install-home/.local/share/applications/aim-bat.desktop".to_owned(),
|
||||||
],
|
],
|
||||||
remaining_apps: Vec::new(),
|
remaining_apps: Vec::new(),
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
|
@ -101,7 +101,7 @@ fn removal_summary_lists_removed_files() {
|
||||||
|
|
||||||
assert!(output.contains("Removed files"));
|
assert!(output.contains("Removed files"));
|
||||||
assert!(output.contains("bat.AppImage"));
|
assert!(output.contains("bat.AppImage"));
|
||||||
assert!(output.contains("upm-bat.desktop"));
|
assert!(output.contains("aim-bat.desktop"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -146,10 +146,10 @@ fn install_summary_omits_completed_steps_recap() {
|
||||||
install: Some(InstallMetadata {
|
install: Some(InstallMetadata {
|
||||||
scope: InstallScope::User,
|
scope: InstallScope::User,
|
||||||
payload_path: Some(
|
payload_path: Some(
|
||||||
"/tmp/install-home/.local/lib/upm/appimages/sharkdp-bat.AppImage".to_owned(),
|
"/tmp/install-home/.local/lib/aim/appimages/sharkdp-bat.AppImage".to_owned(),
|
||||||
),
|
),
|
||||||
desktop_entry_path: Some(
|
desktop_entry_path: Some(
|
||||||
"/tmp/install-home/.local/share/applications/upm-sharkdp-bat.desktop"
|
"/tmp/install-home/.local/share/applications/aim-sharkdp-bat.desktop"
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
),
|
),
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
|
|
@ -176,12 +176,12 @@ fn install_summary_omits_completed_steps_recap() {
|
||||||
tracks_latest: true,
|
tracks_latest: true,
|
||||||
},
|
},
|
||||||
install_scope: InstallScope::User,
|
install_scope: InstallScope::User,
|
||||||
integration_mode: upm_core::integration::policy::IntegrationMode::Full,
|
integration_mode: aim_core::integration::policy::IntegrationMode::Full,
|
||||||
install_outcome: InstallOutcome {
|
install_outcome: InstallOutcome {
|
||||||
final_payload_path: "/tmp/install-home/.local/lib/upm/appimages/sharkdp-bat.AppImage"
|
final_payload_path: "/tmp/install-home/.local/lib/aim/appimages/sharkdp-bat.AppImage"
|
||||||
.into(),
|
.into(),
|
||||||
desktop_entry_path: Some(
|
desktop_entry_path: Some(
|
||||||
"/tmp/install-home/.local/share/applications/upm-sharkdp-bat.desktop".into(),
|
"/tmp/install-home/.local/share/applications/aim-sharkdp-bat.desktop".into(),
|
||||||
),
|
),
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
|
|
@ -285,8 +285,8 @@ fn installed_show_summary_renders_source_scope_and_paths() {
|
||||||
install_scope: Some(InstallScope::User),
|
install_scope: Some(InstallScope::User),
|
||||||
tracked_paths: TrackedInstallPaths {
|
tracked_paths: TrackedInstallPaths {
|
||||||
payload_path: Some("/tmp/bat.AppImage".to_owned()),
|
payload_path: Some("/tmp/bat.AppImage".to_owned()),
|
||||||
desktop_entry_path: Some("/tmp/upm-bat.desktop".to_owned()),
|
desktop_entry_path: Some("/tmp/aim-bat.desktop".to_owned()),
|
||||||
icon_path: Some("/tmp/upm-bat.png".to_owned()),
|
icon_path: Some("/tmp/aim-bat.png".to_owned()),
|
||||||
},
|
},
|
||||||
update_strategy: Some(UpdateStrategySummary {
|
update_strategy: Some(UpdateStrategySummary {
|
||||||
preferred: UpdateChannelSummary {
|
preferred: UpdateChannelSummary {
|
||||||
|
|
@ -325,30 +325,30 @@ fn installed_show_summary_renders_source_scope_and_paths() {
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Source"),
|
muted_bold_label("Source"),
|
||||||
upm::ui::theme::muted("github - sharkdp/bat")
|
aim_cli::ui::theme::muted("github - sharkdp/bat")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Update Mechanism"),
|
muted_bold_label("Update Mechanism"),
|
||||||
upm::ui::theme::muted("electron-builder")
|
aim_cli::ui::theme::muted("electron-builder")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Architecture"),
|
muted_bold_label("Architecture"),
|
||||||
upm::ui::theme::muted("x86_64")
|
aim_cli::ui::theme::muted("x86_64")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Checksum"),
|
muted_bold_label("Checksum"),
|
||||||
upm::ui::theme::muted("sha256:abcdefg...456789")
|
aim_cli::ui::theme::muted("sha256:abcdefg...456789")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&muted_bold_label("Installed as User")));
|
assert!(output.contains(&muted_bold_label("Installed as User")));
|
||||||
assert!(output.contains("/tmp/bat.AppImage"));
|
assert!(output.contains("/tmp/bat.AppImage"));
|
||||||
assert!(output.contains("/tmp/upm-bat.desktop"));
|
assert!(output.contains("/tmp/aim-bat.desktop"));
|
||||||
assert!(!output.contains("[up to date] User"));
|
assert!(!output.contains("[up to date] User"));
|
||||||
assert!(!output.contains("past version"));
|
assert!(!output.contains("past version"));
|
||||||
assert!(!output.contains(&upm::ui::theme::label("Metadata")));
|
assert!(!output.contains(&aim_cli::ui::theme::label("Metadata")));
|
||||||
assert!(!output.contains(&upm::ui::theme::label("Files")));
|
assert!(!output.contains(&aim_cli::ui::theme::label("Files")));
|
||||||
assert!(!output.contains("abcdefghijklmnopqrstuvwxyz0123456789"));
|
assert!(!output.contains("abcdefghijklmnopqrstuvwxyz0123456789"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,24 +427,24 @@ fn installed_show_summary_reports_when_newer_versions_are_available() {
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Source"),
|
muted_bold_label("Source"),
|
||||||
upm::ui::theme::muted("github - pingdotgg/t3code")
|
aim_cli::ui::theme::muted("github - pingdotgg/t3code")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Update Mechanism"),
|
muted_bold_label("Update Mechanism"),
|
||||||
upm::ui::theme::muted("electron-builder")
|
aim_cli::ui::theme::muted("electron-builder")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Architecture"),
|
muted_bold_label("Architecture"),
|
||||||
upm::ui::theme::muted("x86_64")
|
aim_cli::ui::theme::muted("x86_64")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&muted_bold_label("Installed as User")));
|
assert!(output.contains(&muted_bold_label("Installed as User")));
|
||||||
assert!(!output.contains("[update available] User"));
|
assert!(!output.contains("[update available] User"));
|
||||||
assert!(!output.contains("past versions"));
|
assert!(!output.contains("past versions"));
|
||||||
assert!(!output.contains("latest v0.0.16"));
|
assert!(!output.contains("latest v0.0.16"));
|
||||||
assert!(!output.contains(&upm::ui::theme::label("Metadata")));
|
assert!(!output.contains(&aim_cli::ui::theme::label("Metadata")));
|
||||||
assert!(!output.contains(&upm::ui::theme::label("Files")));
|
assert!(!output.contains(&aim_cli::ui::theme::label("Files")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -463,7 +463,7 @@ fn installed_show_list_renders_each_app_using_singular_show_format() {
|
||||||
install_scope: Some(InstallScope::User),
|
install_scope: Some(InstallScope::User),
|
||||||
tracked_paths: TrackedInstallPaths {
|
tracked_paths: TrackedInstallPaths {
|
||||||
payload_path: Some("/tmp/bat.AppImage".to_owned()),
|
payload_path: Some("/tmp/bat.AppImage".to_owned()),
|
||||||
desktop_entry_path: Some("/tmp/upm-bat.desktop".to_owned()),
|
desktop_entry_path: Some("/tmp/aim-bat.desktop".to_owned()),
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
},
|
},
|
||||||
update_strategy: None,
|
update_strategy: None,
|
||||||
|
|
@ -512,12 +512,12 @@ fn installed_show_list_renders_each_app_using_singular_show_format() {
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Source"),
|
muted_bold_label("Source"),
|
||||||
upm::ui::theme::muted("github - sharkdp/bat")
|
aim_cli::ui::theme::muted("github - sharkdp/bat")
|
||||||
)));
|
)));
|
||||||
assert!(output.contains(&format!(
|
assert!(output.contains(&format!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
muted_bold_label("Source"),
|
muted_bold_label("Source"),
|
||||||
upm::ui::theme::muted("github - pingdotgg/t3code")
|
aim_cli::ui::theme::muted("github - pingdotgg/t3code")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "upm-core"
|
name = "aim-core"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
@ -17,8 +17,6 @@ serde.workspace = true
|
||||||
serde_yaml.workspace = true
|
serde_yaml.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
upm-appimage = { path = "../upm-appimage" }
|
|
||||||
upm-module-api = { path = "../upm-module-api" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod appimagehub;
|
||||||
pub mod direct_url;
|
pub mod direct_url;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
pub mod gitlab;
|
pub mod gitlab;
|
||||||
|
|
@ -10,7 +11,14 @@ use crate::adapters::traits::SourceAdapter;
|
||||||
use crate::domain::source::SourceRef;
|
use crate::domain::source::SourceRef;
|
||||||
|
|
||||||
pub fn all_adapter_kinds() -> Vec<&'static str> {
|
pub fn all_adapter_kinds() -> Vec<&'static str> {
|
||||||
vec!["github", "gitlab", "direct-url", "zsync", "sourceforge"]
|
vec![
|
||||||
|
"appimagehub",
|
||||||
|
"github",
|
||||||
|
"gitlab",
|
||||||
|
"direct-url",
|
||||||
|
"zsync",
|
||||||
|
"sourceforge",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supports_source<A: SourceAdapter + ?Sized>(adapter: &A, source: &SourceRef) -> bool {
|
pub fn supports_source<A: SourceAdapter + ?Sized>(adapter: &A, source: &SourceRef) -> bool {
|
||||||
|
|
@ -59,8 +59,7 @@ pub trait SourceAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supports_source(&self, source: &SourceRef) -> bool {
|
fn supports_source(&self, source: &SourceRef) -> bool {
|
||||||
self.repository_source_kind() == Some(source.kind)
|
crate::adapters::supports_source(self, source)
|
||||||
|| self.exact_source_kind() == Some(source.kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_source(&self, source: &SourceRef) -> Result<AdapterResolveOutcome, AdapterError> {
|
fn resolve_source(&self, source: &SourceRef) -> Result<AdapterResolveOutcome, AdapterError> {
|
||||||
|
|
@ -3,6 +3,7 @@ use std::fs::{self, File};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::adapters::appimagehub::AppImageHubAdapter;
|
||||||
use crate::adapters::direct_url::DirectUrlAdapter;
|
use crate::adapters::direct_url::DirectUrlAdapter;
|
||||||
use crate::adapters::gitlab::GitLabAdapter;
|
use crate::adapters::gitlab::GitLabAdapter;
|
||||||
use crate::adapters::sourceforge::SourceForgeAdapter;
|
use crate::adapters::sourceforge::SourceForgeAdapter;
|
||||||
|
|
@ -13,7 +14,6 @@ use crate::app::interaction::{InteractionKind, InteractionRequest};
|
||||||
use crate::app::progress::{
|
use crate::app::progress::{
|
||||||
NoopReporter, OperationEvent, OperationKind, OperationStage, ProgressReporter,
|
NoopReporter, OperationEvent, OperationKind, OperationStage, ProgressReporter,
|
||||||
};
|
};
|
||||||
use crate::app::providers::{ExternalAddResolution, ProviderRegistry};
|
|
||||||
use crate::app::query::{ResolveQueryError, resolve_query};
|
use crate::app::query::{ResolveQueryError, resolve_query};
|
||||||
use crate::app::scope::{ScopeOverride, resolve_install_scope_with_default};
|
use crate::app::scope::{ScopeOverride, resolve_install_scope_with_default};
|
||||||
use crate::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
use crate::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
||||||
|
|
@ -25,14 +25,14 @@ use crate::integration::install::{
|
||||||
use crate::integration::policy::{IntegrationMode, resolve_install_policy};
|
use crate::integration::policy::{IntegrationMode, resolve_install_policy};
|
||||||
use crate::metadata::parse_document;
|
use crate::metadata::parse_document;
|
||||||
use crate::platform::probe_live_host;
|
use crate::platform::probe_live_host;
|
||||||
|
use crate::source::appimagehub::resolve_appimagehub_item;
|
||||||
use crate::source::github::{
|
use crate::source::github::{
|
||||||
GitHubDiscoveryError, GitHubTransport, discover_github_candidates_with, http_client_policy,
|
GitHubDiscoveryError, GitHubTransport, discover_github_candidates_with, http_client_policy,
|
||||||
};
|
};
|
||||||
use crate::update::channels::build_channels;
|
use crate::update::channels::build_channels;
|
||||||
use crate::update::ranking::{rank_channels, select_artifact, to_preference};
|
use crate::update::ranking::{rank_channels, select_artifact, to_preference};
|
||||||
|
|
||||||
const GLOBAL_FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";
|
const FIXTURE_MODE_ENV: &str = "AIM_GITHUB_FIXTURE_MODE";
|
||||||
const FIXTURE_MODE_ENV: &str = "UPM_GITHUB_FIXTURE_MODE";
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct AddSecurityPolicy {
|
pub struct AddSecurityPolicy {
|
||||||
|
|
@ -42,12 +42,11 @@ pub struct AddSecurityPolicy {
|
||||||
pub fn build_add_plan(query: &str) -> Result<AddPlan, BuildAddPlanError> {
|
pub fn build_add_plan(query: &str) -> Result<AddPlan, BuildAddPlanError> {
|
||||||
let transport = crate::source::github::default_transport();
|
let transport = crate::source::github::default_transport();
|
||||||
let mut reporter = NoopReporter;
|
let mut reporter = NoopReporter;
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
build_add_plan_with_reporter_and_policy(
|
||||||
query,
|
query,
|
||||||
transport.as_ref(),
|
transport.as_ref(),
|
||||||
&mut reporter,
|
&mut reporter,
|
||||||
AddSecurityPolicy::default(),
|
AddSecurityPolicy::default(),
|
||||||
&ProviderRegistry::default(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,12 +55,11 @@ pub fn build_add_plan_with<T: GitHubTransport + ?Sized>(
|
||||||
transport: &T,
|
transport: &T,
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
) -> Result<AddPlan, BuildAddPlanError> {
|
||||||
let mut reporter = NoopReporter;
|
let mut reporter = NoopReporter;
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
build_add_plan_with_reporter_and_policy(
|
||||||
query,
|
query,
|
||||||
transport,
|
transport,
|
||||||
&mut reporter,
|
&mut reporter,
|
||||||
AddSecurityPolicy::default(),
|
AddSecurityPolicy::default(),
|
||||||
&ProviderRegistry::default(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,40 +68,11 @@ pub fn build_add_plan_with_reporter<T: GitHubTransport + ?Sized>(
|
||||||
transport: &T,
|
transport: &T,
|
||||||
reporter: &mut impl ProgressReporter,
|
reporter: &mut impl ProgressReporter,
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
) -> Result<AddPlan, BuildAddPlanError> {
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
build_add_plan_with_reporter_and_policy(
|
||||||
query,
|
query,
|
||||||
transport,
|
transport,
|
||||||
reporter,
|
reporter,
|
||||||
AddSecurityPolicy::default(),
|
AddSecurityPolicy::default(),
|
||||||
&ProviderRegistry::default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_add_plan_with_registered_providers<T: GitHubTransport + ?Sized>(
|
|
||||||
query: &str,
|
|
||||||
transport: &T,
|
|
||||||
providers: &ProviderRegistry<'_>,
|
|
||||||
policy: AddSecurityPolicy,
|
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
|
||||||
let mut reporter = NoopReporter;
|
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
|
||||||
query,
|
|
||||||
transport,
|
|
||||||
&mut reporter,
|
|
||||||
policy,
|
|
||||||
providers,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_add_plan_with_reporter_and_registered_providers<T: GitHubTransport + ?Sized>(
|
|
||||||
query: &str,
|
|
||||||
transport: &T,
|
|
||||||
reporter: &mut impl ProgressReporter,
|
|
||||||
providers: &ProviderRegistry<'_>,
|
|
||||||
policy: AddSecurityPolicy,
|
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
|
||||||
query, transport, reporter, policy, providers,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,22 +81,6 @@ pub fn build_add_plan_with_reporter_and_policy<T: GitHubTransport + ?Sized>(
|
||||||
transport: &T,
|
transport: &T,
|
||||||
reporter: &mut impl ProgressReporter,
|
reporter: &mut impl ProgressReporter,
|
||||||
policy: AddSecurityPolicy,
|
policy: AddSecurityPolicy,
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
|
||||||
build_add_plan_with_reporter_and_policy_and_registry(
|
|
||||||
query,
|
|
||||||
transport,
|
|
||||||
reporter,
|
|
||||||
policy,
|
|
||||||
&ProviderRegistry::default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Sized>(
|
|
||||||
query: &str,
|
|
||||||
transport: &T,
|
|
||||||
reporter: &mut impl ProgressReporter,
|
|
||||||
policy: AddSecurityPolicy,
|
|
||||||
providers: &ProviderRegistry<'_>,
|
|
||||||
) -> Result<AddPlan, BuildAddPlanError> {
|
) -> Result<AddPlan, BuildAddPlanError> {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
stage: OperationStage::ResolveQuery,
|
stage: OperationStage::ResolveQuery,
|
||||||
|
|
@ -138,7 +91,8 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
|
|
||||||
let mut interactions = Vec::new();
|
let mut interactions = Vec::new();
|
||||||
let mut parsed_metadata = Vec::new();
|
let mut parsed_metadata = Vec::new();
|
||||||
let (resolution, selected_artifact, update_strategy, display_name_hint) = match source.kind {
|
let mut display_name_hint = None;
|
||||||
|
let (resolution, selected_artifact, update_strategy) = match source.kind {
|
||||||
SourceKind::GitHub => {
|
SourceKind::GitHub => {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
stage: OperationStage::DiscoverRelease,
|
stage: OperationStage::DiscoverRelease,
|
||||||
|
|
@ -194,7 +148,6 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
},
|
},
|
||||||
artifact,
|
artifact,
|
||||||
strategy,
|
strategy,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SourceKind::GitLab => {
|
SourceKind::GitLab => {
|
||||||
|
|
@ -235,29 +188,59 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
selection_reason: "provider-release".to_owned(),
|
selection_reason: "provider-release".to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(resolution, artifact, strategy, None)
|
(resolution, artifact, strategy)
|
||||||
}
|
}
|
||||||
SourceKind::AppImageHub => {
|
SourceKind::AppImageHub => {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
stage: OperationStage::DiscoverRelease,
|
stage: OperationStage::DiscoverRelease,
|
||||||
message: "discovering release".to_owned(),
|
message: "discovering release".to_owned(),
|
||||||
});
|
});
|
||||||
if let Some(external_resolution) =
|
let adapter = AppImageHubAdapter;
|
||||||
resolve_registered_external_add_provider(&source, providers)?
|
let resolution = match adapter
|
||||||
|
.resolve_source(&source)
|
||||||
|
.map_err(|error| BuildAddPlanError::Adapter("appimagehub", error))?
|
||||||
{
|
{
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
AdapterResolveOutcome::Resolved(resolution) => resolution,
|
||||||
stage: OperationStage::SelectArtifact,
|
AdapterResolveOutcome::NoInstallableArtifact { source } => {
|
||||||
message: "selecting artifact".to_owned(),
|
return Err(BuildAddPlanError::NoInstallableArtifact { source });
|
||||||
});
|
}
|
||||||
(
|
};
|
||||||
external_resolution.resolution,
|
let resolved_item = resolve_appimagehub_item(&resolution.source)
|
||||||
external_resolution.selected_artifact,
|
.map_err(|error| {
|
||||||
external_resolution.update_strategy,
|
BuildAddPlanError::Adapter(
|
||||||
external_resolution.display_name_hint,
|
"appimagehub",
|
||||||
)
|
crate::adapters::traits::AdapterError::ResolutionFailed(format!(
|
||||||
} else {
|
"{error:?}"
|
||||||
return Err(BuildAddPlanError::NoInstallableArtifact { source });
|
)),
|
||||||
}
|
)
|
||||||
|
})?
|
||||||
|
.ok_or(BuildAddPlanError::NoInstallableArtifact {
|
||||||
|
source: resolution.source.clone(),
|
||||||
|
})?;
|
||||||
|
display_name_hint = Some(resolved_item.title.clone());
|
||||||
|
|
||||||
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
|
stage: OperationStage::SelectArtifact,
|
||||||
|
message: "selecting artifact".to_owned(),
|
||||||
|
});
|
||||||
|
let artifact = ArtifactCandidate {
|
||||||
|
url: resolved_item.download.url.clone(),
|
||||||
|
version: resolved_item.version.clone(),
|
||||||
|
arch: resolved_item.download.arch.clone(),
|
||||||
|
trusted_checksum: None,
|
||||||
|
weak_checksum_md5: resolved_item.download.md5sum.clone(),
|
||||||
|
selection_reason: "provider-release".to_owned(),
|
||||||
|
};
|
||||||
|
let strategy = UpdateStrategy {
|
||||||
|
preferred: crate::domain::update::ChannelPreference {
|
||||||
|
kind: crate::domain::update::UpdateChannelKind::DirectAsset,
|
||||||
|
locator: resolved_item.download.url.clone(),
|
||||||
|
reason: "provider-release".to_owned(),
|
||||||
|
},
|
||||||
|
alternates: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(resolution, artifact, strategy)
|
||||||
}
|
}
|
||||||
SourceKind::DirectUrl => {
|
SourceKind::DirectUrl => {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
|
|
@ -291,7 +274,7 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
alternates: Vec::new(),
|
alternates: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(resolution, artifact, strategy, None)
|
(resolution, artifact, strategy)
|
||||||
}
|
}
|
||||||
SourceKind::SourceForge => {
|
SourceKind::SourceForge => {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
|
|
@ -332,7 +315,7 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
alternates: Vec::new(),
|
alternates: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
(resolution, artifact, strategy, None)
|
(resolution, artifact, strategy)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
reporter.report(&OperationEvent::StageChanged {
|
reporter.report(&OperationEvent::StageChanged {
|
||||||
|
|
@ -362,7 +345,7 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
},
|
},
|
||||||
alternates: Vec::new(),
|
alternates: Vec::new(),
|
||||||
};
|
};
|
||||||
(resolution, artifact, strategy, None)
|
(resolution, artifact, strategy)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -376,21 +359,6 @@ fn build_add_plan_with_reporter_and_policy_and_registry<T: GitHubTransport + ?Si
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_registered_external_add_provider(
|
|
||||||
source: &crate::domain::source::SourceRef,
|
|
||||||
providers: &ProviderRegistry<'_>,
|
|
||||||
) -> Result<Option<ExternalAddResolution>, BuildAddPlanError> {
|
|
||||||
for provider in &providers.external_add_providers {
|
|
||||||
match provider.resolve(source) {
|
|
||||||
Ok(Some(resolution)) => return Ok(Some(resolution)),
|
|
||||||
Ok(None) => continue,
|
|
||||||
Err(error) => return Err(BuildAddPlanError::Adapter(provider.id(), error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefer_latest_tracking(mut plan: AddPlan) -> AddPlan {
|
pub fn prefer_latest_tracking(mut plan: AddPlan) -> AddPlan {
|
||||||
if let Some(index) = plan
|
if let Some(index) = plan
|
||||||
.update_strategy
|
.update_strategy
|
||||||
|
|
@ -497,7 +465,7 @@ pub fn install_app_with_reporter(
|
||||||
install_home,
|
install_home,
|
||||||
&policy
|
&policy
|
||||||
.desktop_entry_root
|
.desktop_entry_root
|
||||||
.join(format!("upm-{}.desktop", record.stable_id)),
|
.join(format!("aim-{}.desktop", record.stable_id)),
|
||||||
);
|
);
|
||||||
let icon_path = resolve_target_path(
|
let icon_path = resolve_target_path(
|
||||||
install_home,
|
install_home,
|
||||||
|
|
@ -507,7 +475,7 @@ pub fn install_app_with_reporter(
|
||||||
stage: OperationStage::DownloadArtifact,
|
stage: OperationStage::DownloadArtifact,
|
||||||
message: "downloading artifact".to_owned(),
|
message: "downloading artifact".to_owned(),
|
||||||
});
|
});
|
||||||
let staging_root = install_home.join(".local/share/upm/staging");
|
let staging_root = install_home.join(".local/share/aim/staging");
|
||||||
let staged_payload_path = staged_appimage_path(&staging_root, &record.stable_id);
|
let staged_payload_path = staged_appimage_path(&staging_root, &record.stable_id);
|
||||||
let artifact_size_bytes = download_artifact_to_staged_path_with_reporter(
|
let artifact_size_bytes = download_artifact_to_staged_path_with_reporter(
|
||||||
&plan.selected_artifact.url,
|
&plan.selected_artifact.url,
|
||||||
|
|
@ -662,9 +630,7 @@ fn download_artifact_to_staged_path_with_reporter(
|
||||||
) -> Result<u64, InstallAppError> {
|
) -> Result<u64, InstallAppError> {
|
||||||
let policy = http_client_policy();
|
let policy = http_client_policy();
|
||||||
|
|
||||||
if env::var(GLOBAL_FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
if env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1") {
|
||||||
|| env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
|
||||||
{
|
|
||||||
let bytes = b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82";
|
let bytes = b"\x7fELFAppImage\x89PNG\r\n\x1a\nicondataIEND\xaeB`\x82";
|
||||||
return download_to_staged_path_with_retries(staged_payload_path, reporter, policy, || {
|
return download_to_staged_path_with_retries(staged_payload_path, reporter, policy, || {
|
||||||
Ok((
|
Ok((
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
pub mod add;
|
pub mod add;
|
||||||
pub mod application;
|
|
||||||
pub mod identity;
|
pub mod identity;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
pub mod providers;
|
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod remove;
|
pub mod remove;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod show;
|
pub mod show;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
||||||
pub use application::{UpmApp, UpmAppBuilder};
|
|
||||||
|
|
@ -1,15 +1,35 @@
|
||||||
use crate::app::providers::ProviderRegistry;
|
|
||||||
use crate::domain::app::AppRecord;
|
use crate::domain::app::AppRecord;
|
||||||
use crate::domain::search::{
|
use crate::domain::search::{
|
||||||
InstalledSearchMatch, SearchInstallStatus, SearchQuery, SearchResult, SearchResults,
|
InstalledSearchMatch, SearchInstallStatus, SearchQuery, SearchResult, SearchResults,
|
||||||
SearchWarning,
|
SearchWarning,
|
||||||
};
|
};
|
||||||
|
use crate::source::appimagehub::{
|
||||||
|
AppImageHubSearchError, AppImageHubTransport, search_appimagehub_with,
|
||||||
|
};
|
||||||
use crate::source::github::{
|
use crate::source::github::{
|
||||||
GitHubSearchError, GitHubTransport, TransportRelease, default_transport,
|
GitHubSearchError, GitHubTransport, TransportRelease, default_transport,
|
||||||
search_github_repositories_with,
|
search_github_repositories_with,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
pub use upm_module_api::app::search::{SearchProvider, SearchProviderError};
|
|
||||||
|
pub trait SearchProvider {
|
||||||
|
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct SearchProviderError {
|
||||||
|
pub provider_id: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchProviderError {
|
||||||
|
pub fn new(provider_id: &str, message: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
provider_id: provider_id.to_owned(),
|
||||||
|
message: message.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum SearchError {
|
pub enum SearchError {
|
||||||
|
|
@ -20,31 +40,17 @@ pub fn build_search_results(
|
||||||
query: &SearchQuery,
|
query: &SearchQuery,
|
||||||
installed_apps: &[AppRecord],
|
installed_apps: &[AppRecord],
|
||||||
) -> Result<SearchResults, SearchError> {
|
) -> Result<SearchResults, SearchError> {
|
||||||
build_search_results_with_registered_providers(
|
let github_transport = default_transport();
|
||||||
|
let appimagehub_transport = crate::source::appimagehub::default_transport();
|
||||||
|
let github_provider = GitHubSearchProvider::new(github_transport.as_ref());
|
||||||
|
let appimagehub_provider = AppImageHubSearchProvider::new(appimagehub_transport.as_ref());
|
||||||
|
build_search_results_with(
|
||||||
query,
|
query,
|
||||||
installed_apps,
|
installed_apps,
|
||||||
&ProviderRegistry::default(),
|
&[&github_provider, &appimagehub_provider],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_search_results_with_registered_providers(
|
|
||||||
query: &SearchQuery,
|
|
||||||
installed_apps: &[AppRecord],
|
|
||||||
providers: &ProviderRegistry<'_>,
|
|
||||||
) -> Result<SearchResults, SearchError> {
|
|
||||||
let github_transport = default_transport();
|
|
||||||
let github_provider = GitHubSearchProvider::new(github_transport.as_ref());
|
|
||||||
let mut resolved_providers = vec![&github_provider as &dyn SearchProvider];
|
|
||||||
resolved_providers.extend(
|
|
||||||
providers
|
|
||||||
.search_providers
|
|
||||||
.iter()
|
|
||||||
.map(|provider| provider.as_ref() as &dyn SearchProvider),
|
|
||||||
);
|
|
||||||
|
|
||||||
build_search_results_with(query, installed_apps, &resolved_providers)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_search_results_with(
|
pub fn build_search_results_with(
|
||||||
query: &SearchQuery,
|
query: &SearchQuery,
|
||||||
installed_apps: &[AppRecord],
|
installed_apps: &[AppRecord],
|
||||||
|
|
@ -88,6 +94,58 @@ impl<'a, T: GitHubTransport + ?Sized> GitHubSearchProvider<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AppImageHubSearchProvider<'a, T: AppImageHubTransport + ?Sized> {
|
||||||
|
transport: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: AppImageHubTransport + ?Sized> AppImageHubSearchProvider<'a, T> {
|
||||||
|
pub fn new(transport: &'a T) -> Self {
|
||||||
|
Self { transport }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AppImageHubTransport + ?Sized> SearchProvider for AppImageHubSearchProvider<'_, T> {
|
||||||
|
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError> {
|
||||||
|
let hits = search_appimagehub_with(&query.text, query.remote_limit, self.transport)
|
||||||
|
.map_err(|error| {
|
||||||
|
SearchProviderError::new("appimagehub", &render_appimagehub_search_error(&error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let normalized_query = normalize_lookup(&query.text);
|
||||||
|
let mut ranked_hits = hits
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, hit)| {
|
||||||
|
(
|
||||||
|
appimagehub_remote_match_rank(
|
||||||
|
&normalized_query,
|
||||||
|
&hit.name,
|
||||||
|
hit.summary.as_deref(),
|
||||||
|
),
|
||||||
|
index,
|
||||||
|
hit,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
ranked_hits.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1)));
|
||||||
|
|
||||||
|
Ok(ranked_hits
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, _, hit)| SearchResult {
|
||||||
|
provider_id: "appimagehub".to_owned(),
|
||||||
|
display_name: hit.name,
|
||||||
|
description: hit.summary,
|
||||||
|
source_locator: hit.detail_page,
|
||||||
|
install_query: format!("appimagehub/{}", hit.id),
|
||||||
|
canonical_locator: hit.id,
|
||||||
|
version: Some(hit.version),
|
||||||
|
install_status: SearchInstallStatus::Available,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: GitHubTransport + ?Sized> SearchProvider for GitHubSearchProvider<'_, T> {
|
impl<T: GitHubTransport + ?Sized> SearchProvider for GitHubSearchProvider<'_, T> {
|
||||||
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError> {
|
fn search(&self, query: &SearchQuery) -> Result<Vec<SearchResult>, SearchProviderError> {
|
||||||
let name_only_query = format!("{} in:name", query.text);
|
let name_only_query = format!("{} in:name", query.text);
|
||||||
|
|
@ -334,3 +392,45 @@ fn render_github_search_error(error: &GitHubSearchError) -> String {
|
||||||
GitHubSearchError::Transport(inner) => inner.to_string(),
|
GitHubSearchError::Transport(inner) => inner.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn appimagehub_remote_match_rank(query: &str, name: &str, summary: Option<&str>) -> u8 {
|
||||||
|
let name = normalize_lookup(name);
|
||||||
|
let summary = summary.map(normalize_lookup);
|
||||||
|
|
||||||
|
if name == query {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.starts_with(query) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if name.contains(query) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary
|
||||||
|
.as_deref()
|
||||||
|
.map(|summary| summary.starts_with(query))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if summary
|
||||||
|
.as_deref()
|
||||||
|
.map(|summary| summary.contains(query))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
5
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_appimagehub_search_error(error: &AppImageHubSearchError) -> String {
|
||||||
|
match error {
|
||||||
|
AppImageHubSearchError::Parse(inner) => inner.to_string(),
|
||||||
|
AppImageHubSearchError::Transport(inner) => inner.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -291,7 +291,7 @@ fn stage_existing_installation(
|
||||||
}
|
}
|
||||||
|
|
||||||
let stage_dir = install_home
|
let stage_dir = install_home
|
||||||
.join(".local/share/upm/rollback")
|
.join(".local/share/aim/rollback")
|
||||||
.join(&app.stable_id);
|
.join(&app.stable_id);
|
||||||
fs::create_dir_all(&stage_dir)
|
fs::create_dir_all(&stage_dir)
|
||||||
.map_err(|error| format!("failed to create rollback staging directory: {error}"))?;
|
.map_err(|error| format!("failed to create rollback staging directory: {error}"))?;
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use crate::domain::app::AppRecord;
|
use crate::domain::app::AppRecord;
|
||||||
pub use upm_module_api::domain::update::{
|
|
||||||
ArtifactCandidate, ChannelPreference, UpdateChannelKind, UpdateStrategy,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub enum ParsedMetadataKind {
|
pub enum ParsedMetadataKind {
|
||||||
|
|
@ -34,6 +31,25 @@ pub struct ParsedMetadata {
|
||||||
pub confidence: u8,
|
pub confidence: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub enum UpdateChannelKind {
|
||||||
|
GitHubReleases,
|
||||||
|
ElectronBuilder,
|
||||||
|
Zsync,
|
||||||
|
DirectAsset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateChannelKind {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::GitHubReleases => "github-releases",
|
||||||
|
Self::ElectronBuilder => "electron-builder",
|
||||||
|
Self::Zsync => "zsync",
|
||||||
|
Self::DirectAsset => "direct-asset-lineage",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct UpdateChannel {
|
pub struct UpdateChannel {
|
||||||
pub kind: UpdateChannelKind,
|
pub kind: UpdateChannelKind,
|
||||||
|
|
@ -50,6 +66,30 @@ pub struct UpdateChannel {
|
||||||
pub prerelease: bool,
|
pub prerelease: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ChannelPreference {
|
||||||
|
pub kind: UpdateChannelKind,
|
||||||
|
pub locator: String,
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct UpdateStrategy {
|
||||||
|
pub preferred: ChannelPreference,
|
||||||
|
#[serde(default)]
|
||||||
|
pub alternates: Vec<ChannelPreference>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct ArtifactCandidate {
|
||||||
|
pub url: String,
|
||||||
|
pub version: String,
|
||||||
|
pub arch: Option<String>,
|
||||||
|
pub trusted_checksum: Option<String>,
|
||||||
|
pub weak_checksum_md5: Option<String>,
|
||||||
|
pub selection_reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct UpdatePlan {
|
pub struct UpdatePlan {
|
||||||
pub items: Vec<PlannedUpdate>,
|
pub items: Vec<PlannedUpdate>,
|
||||||
|
|
@ -11,7 +11,7 @@ pub fn managed_appimage_path(home_dir: &Path, scope: InstallScope, app_id: &str)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn desktop_entry_path(home_dir: &Path, scope: InstallScope, app_id: &str) -> PathBuf {
|
pub fn desktop_entry_path(home_dir: &Path, scope: InstallScope, app_id: &str) -> PathBuf {
|
||||||
scope_applications_dir(home_dir, scope).join(format!("upm-{app_id}.desktop"))
|
scope_applications_dir(home_dir, scope).join(format!("aim-{app_id}.desktop"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn icon_path(home_dir: &Path, scope: InstallScope, app_id: &str) -> PathBuf {
|
pub fn icon_path(home_dir: &Path, scope: InstallScope, app_id: &str) -> PathBuf {
|
||||||
|
|
@ -37,7 +37,7 @@ pub fn resolve_install_policy(
|
||||||
(DistroFamily::Immutable, InstallScope::System) if capabilities.is_immutable => {
|
(DistroFamily::Immutable, InstallScope::System) if capabilities.is_immutable => {
|
||||||
Ok(InstallPolicy {
|
Ok(InstallPolicy {
|
||||||
scope: InstallScope::User,
|
scope: InstallScope::User,
|
||||||
payload_root: PathBuf::from(".local/lib/upm/appimages"),
|
payload_root: PathBuf::from(".local/lib/aim/appimages"),
|
||||||
desktop_entry_root: PathBuf::from(".local/share/applications"),
|
desktop_entry_root: PathBuf::from(".local/share/applications"),
|
||||||
icon_root: PathBuf::from(".local/share/icons/hicolor/256x256/apps"),
|
icon_root: PathBuf::from(".local/share/icons/hicolor/256x256/apps"),
|
||||||
integration_mode: IntegrationMode::Degraded,
|
integration_mode: IntegrationMode::Degraded,
|
||||||
|
|
@ -57,7 +57,7 @@ pub fn resolve_install_policy(
|
||||||
}),
|
}),
|
||||||
_ => Ok(InstallPolicy {
|
_ => Ok(InstallPolicy {
|
||||||
scope: InstallScope::User,
|
scope: InstallScope::User,
|
||||||
payload_root: PathBuf::from(".local/lib/upm/appimages"),
|
payload_root: PathBuf::from(".local/lib/aim/appimages"),
|
||||||
desktop_entry_root: PathBuf::from(".local/share/applications"),
|
desktop_entry_root: PathBuf::from(".local/share/applications"),
|
||||||
icon_root: PathBuf::from(".local/share/icons/hicolor/256x256/apps"),
|
icon_root: PathBuf::from(".local/share/icons/hicolor/256x256/apps"),
|
||||||
integration_mode: if capabilities.has_desktop_session {
|
integration_mode: if capabilities.has_desktop_session {
|
||||||
|
|
@ -62,7 +62,7 @@ fn icon_theme_root(icon_path: &Path) -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audit_helper(helper: &Path, args: &[&Path]) {
|
fn audit_helper(helper: &Path, args: &[&Path]) {
|
||||||
if env::var("UPM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
if env::var("AIM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ fn audit_helper(helper: &Path, args: &[&Path]) {
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[upm] helper exec: {}{}{}",
|
"[aim] helper exec: {}{}{}",
|
||||||
helper.display(),
|
helper.display(),
|
||||||
if rendered_args.is_empty() { "" } else { " " },
|
if rendered_args.is_empty() { "" } else { " " },
|
||||||
rendered_args
|
rendered_args
|
||||||
|
|
@ -80,23 +80,23 @@ fn audit_helper(helper: &Path, args: &[&Path]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audit_helper_status(helper: &Path, code: Option<i32>) {
|
fn audit_helper_status(helper: &Path, code: Option<i32>) {
|
||||||
if env::var("UPM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
if env::var("AIM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match code {
|
match code {
|
||||||
Some(code) => eprintln!("[upm] helper exit: {} code={code}", helper.display()),
|
Some(code) => eprintln!("[aim] helper exit: {} code={code}", helper.display()),
|
||||||
None => eprintln!(
|
None => eprintln!(
|
||||||
"[upm] helper exit: {} terminated by signal",
|
"[aim] helper exit: {} terminated by signal",
|
||||||
helper.display()
|
helper.display()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn audit_helper_failure(helper: &Path, error: &str) {
|
fn audit_helper_failure(helper: &Path, error: &str) {
|
||||||
if env::var("UPM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
if env::var("AIM_DEBUG_EXTERNAL_HELPERS").ok().as_deref() != Some("1") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("[upm] helper failure: {} error={error}", helper.display());
|
eprintln!("[aim] helper failure: {} error={error}", helper.display());
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,3 @@ pub mod platform;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
||||||
pub use app::providers::{ExternalAddProvider, ExternalAddResolution, ProviderRegistry};
|
|
||||||
pub use app::{UpmApp, UpmAppBuilder};
|
|
||||||
|
|
@ -71,7 +71,7 @@ fn is_writable_dir(path: &Path) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let probe_path = path.join(".upm-write-test");
|
let probe_path = path.join(".aim-write-test");
|
||||||
let result = OpenOptions::new()
|
let result = OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
|
|
@ -10,11 +10,11 @@ pub use crate::domain::app::InstallScope;
|
||||||
pub use capabilities::{DesktopHelpers, HostCapabilities, WritableRoots};
|
pub use capabilities::{DesktopHelpers, HostCapabilities, WritableRoots};
|
||||||
pub use distro::{DistroFamily, detect_distro_family};
|
pub use distro::{DistroFamily, detect_distro_family};
|
||||||
|
|
||||||
const OS_RELEASE_PATH_ENV: &str = "UPM_OS_RELEASE_PATH";
|
const OS_RELEASE_PATH_ENV: &str = "AIM_OS_RELEASE_PATH";
|
||||||
const HELPER_PATHS_ENV: &str = "UPM_HELPER_PATHS";
|
const HELPER_PATHS_ENV: &str = "AIM_HELPER_PATHS";
|
||||||
|
|
||||||
pub fn user_managed_appimages_dir(home_dir: &Path) -> PathBuf {
|
pub fn user_managed_appimages_dir(home_dir: &Path) -> PathBuf {
|
||||||
home_dir.join(".local/lib/upm/appimages")
|
home_dir.join(".local/lib/aim/appimages")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_applications_dir(home_dir: &Path) -> PathBuf {
|
pub fn user_applications_dir(home_dir: &Path) -> PathBuf {
|
||||||
|
|
@ -26,7 +26,7 @@ pub fn user_icons_dir(home_dir: &Path) -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn system_managed_appimages_dir() -> PathBuf {
|
pub fn system_managed_appimages_dir() -> PathBuf {
|
||||||
PathBuf::from("/opt/upm/appimages")
|
PathBuf::from("/opt/aim/appimages")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn system_applications_dir() -> PathBuf {
|
pub fn system_applications_dir() -> PathBuf {
|
||||||
|
|
@ -4,8 +4,7 @@ use std::time::Duration;
|
||||||
use crate::domain::source::SourceRef;
|
use crate::domain::source::SourceRef;
|
||||||
|
|
||||||
const DEFAULT_APPIMAGEHUB_API_BASE: &str = "https://api.appimagehub.com/ocs/v1/content";
|
const DEFAULT_APPIMAGEHUB_API_BASE: &str = "https://api.appimagehub.com/ocs/v1/content";
|
||||||
const GLOBAL_FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";
|
const FIXTURE_MODE_ENV: &str = "AIM_APPIMAGEHUB_FIXTURE_MODE";
|
||||||
const FIXTURE_MODE_ENV: &str = "UPM_APPIMAGEHUB_FIXTURE_MODE";
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct AppImageHubDownload {
|
pub struct AppImageHubDownload {
|
||||||
|
|
@ -57,8 +56,9 @@ pub trait AppImageHubTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_transport() -> Box<dyn AppImageHubTransport> {
|
pub fn default_transport() -> Box<dyn AppImageHubTransport> {
|
||||||
if env::var(GLOBAL_FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
if env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
||||||
|| env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1") {
|
|| env::var("AIM_GITHUB_FIXTURE_MODE").ok().as_deref() == Some("1")
|
||||||
|
{
|
||||||
Box::new(FixtureAppImageHubTransport)
|
Box::new(FixtureAppImageHubTransport)
|
||||||
} else {
|
} else {
|
||||||
Box::new(ReqwestAppImageHubTransport::new())
|
Box::new(ReqwestAppImageHubTransport::new())
|
||||||
|
|
@ -129,7 +129,7 @@ impl ReqwestAppImageHubTransport {
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.build()
|
.build()
|
||||||
.expect("reqwest client should build"),
|
.expect("reqwest client should build"),
|
||||||
api_base: env::var("UPM_APPIMAGEHUB_API_BASE")
|
api_base: env::var("AIM_APPIMAGEHUB_API_BASE")
|
||||||
.unwrap_or_else(|_| DEFAULT_APPIMAGEHUB_API_BASE.to_owned()),
|
.unwrap_or_else(|_| DEFAULT_APPIMAGEHUB_API_BASE.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -424,11 +424,11 @@ fn resolved_version(item: &AppImageHubItem, download: &AppImageHubDownload) -> S
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fixture_item(id: &str) -> Option<AppImageHubItem> {
|
fn fixture_item(id: &str) -> Option<AppImageHubItem> {
|
||||||
let insecure_http = env::var("UPM_APPIMAGEHUB_FIXTURE_INSECURE_HTTP")
|
let insecure_http = env::var("AIM_APPIMAGEHUB_FIXTURE_INSECURE_HTTP")
|
||||||
.ok()
|
.ok()
|
||||||
.as_deref()
|
.as_deref()
|
||||||
== Some("1");
|
== Some("1");
|
||||||
let bad_md5 = env::var("UPM_APPIMAGEHUB_FIXTURE_BAD_MD5").ok().as_deref() == Some("1");
|
let bad_md5 = env::var("AIM_APPIMAGEHUB_FIXTURE_BAD_MD5").ok().as_deref() == Some("1");
|
||||||
|
|
||||||
match id {
|
match id {
|
||||||
"2338455" => Some(AppImageHubItem {
|
"2338455" => Some(AppImageHubItem {
|
||||||
|
|
@ -5,8 +5,7 @@ use crate::domain::source::{ResolvedRelease, SourceRef};
|
||||||
use crate::metadata::MetadataDocument;
|
use crate::metadata::MetadataDocument;
|
||||||
|
|
||||||
const DEFAULT_GITHUB_API_BASE: &str = "https://api.github.com";
|
const DEFAULT_GITHUB_API_BASE: &str = "https://api.github.com";
|
||||||
const GLOBAL_FIXTURE_MODE_ENV: &str = "UPM_FIXTURE_MODE";
|
const FIXTURE_MODE_ENV: &str = "AIM_GITHUB_FIXTURE_MODE";
|
||||||
const FIXTURE_MODE_ENV: &str = "UPM_GITHUB_FIXTURE_MODE";
|
|
||||||
const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 30;
|
const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 30;
|
||||||
const DEFAULT_HTTP_MAX_RETRIES: usize = 3;
|
const DEFAULT_HTTP_MAX_RETRIES: usize = 3;
|
||||||
|
|
||||||
|
|
@ -177,9 +176,7 @@ pub fn search_github_repositories_with<T: GitHubTransport + ?Sized>(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_transport() -> Box<dyn GitHubTransport> {
|
pub fn default_transport() -> Box<dyn GitHubTransport> {
|
||||||
if env::var(GLOBAL_FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
if env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1") {
|
||||||
|| env::var(FIXTURE_MODE_ENV).ok().as_deref() == Some("1")
|
|
||||||
{
|
|
||||||
Box::new(FixtureGitHubTransport)
|
Box::new(FixtureGitHubTransport)
|
||||||
} else {
|
} else {
|
||||||
Box::new(ReqwestGitHubTransport::new())
|
Box::new(ReqwestGitHubTransport::new())
|
||||||
|
|
@ -203,13 +200,13 @@ impl ReqwestGitHubTransport {
|
||||||
let mut default_headers = reqwest::header::HeaderMap::new();
|
let mut default_headers = reqwest::header::HeaderMap::new();
|
||||||
default_headers.insert(
|
default_headers.insert(
|
||||||
reqwest::header::USER_AGENT,
|
reqwest::header::USER_AGENT,
|
||||||
reqwest::header::HeaderValue::from_static("upm/0.1"),
|
reqwest::header::HeaderValue::from_static("aim/0.1"),
|
||||||
);
|
);
|
||||||
default_headers.insert(
|
default_headers.insert(
|
||||||
reqwest::header::ACCEPT,
|
reqwest::header::ACCEPT,
|
||||||
reqwest::header::HeaderValue::from_static("application/vnd.github+json"),
|
reqwest::header::HeaderValue::from_static("application/vnd.github+json"),
|
||||||
);
|
);
|
||||||
if let Some(token) = env::var("UPM_GITHUB_TOKEN")
|
if let Some(token) = env::var("AIM_GITHUB_TOKEN")
|
||||||
.ok()
|
.ok()
|
||||||
.or_else(|| env::var("GITHUB_TOKEN").ok())
|
.or_else(|| env::var("GITHUB_TOKEN").ok())
|
||||||
&& let Ok(value) = reqwest::header::HeaderValue::from_str(&format!("Bearer {token}"))
|
&& let Ok(value) = reqwest::header::HeaderValue::from_str(&format!("Bearer {token}"))
|
||||||
|
|
@ -223,7 +220,7 @@ impl ReqwestGitHubTransport {
|
||||||
.timeout(policy.timeout)
|
.timeout(policy.timeout)
|
||||||
.build()
|
.build()
|
||||||
.expect("reqwest client should build"),
|
.expect("reqwest client should build"),
|
||||||
api_base: env::var("UPM_GITHUB_API_BASE")
|
api_base: env::var("AIM_GITHUB_API_BASE")
|
||||||
.unwrap_or_else(|_| DEFAULT_GITHUB_API_BASE.to_owned()),
|
.unwrap_or_else(|_| DEFAULT_GITHUB_API_BASE.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod appimagehub;
|
||||||
pub mod github;
|
pub mod github;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
use upm_core::adapters::direct_url::DirectUrlAdapter;
|
use aim_core::adapters::appimagehub::AppImageHubAdapter;
|
||||||
use upm_core::adapters::github::GitHubAdapter;
|
use aim_core::adapters::direct_url::DirectUrlAdapter;
|
||||||
use upm_core::adapters::gitlab::GitLabAdapter;
|
use aim_core::adapters::github::GitHubAdapter;
|
||||||
use upm_core::adapters::sourceforge::SourceForgeAdapter;
|
use aim_core::adapters::gitlab::GitLabAdapter;
|
||||||
use upm_core::adapters::traits::{
|
use aim_core::adapters::sourceforge::SourceForgeAdapter;
|
||||||
|
use aim_core::adapters::traits::{
|
||||||
AdapterCapabilities, AdapterError, AdapterResolution, AdapterResolveOutcome, SourceAdapter,
|
AdapterCapabilities, AdapterError, AdapterResolution, AdapterResolveOutcome, SourceAdapter,
|
||||||
};
|
};
|
||||||
use upm_core::app::query::resolve_query;
|
use aim_core::app::query::resolve_query;
|
||||||
use upm_core::domain::source::{
|
use aim_core::domain::source::{
|
||||||
NormalizedSourceKind, ResolvedRelease, SourceInputKind, SourceKind, SourceRef,
|
NormalizedSourceKind, ResolvedRelease, SourceInputKind, SourceKind, SourceRef,
|
||||||
};
|
};
|
||||||
|
use aim_core::source::appimagehub::FixtureAppImageHubTransport;
|
||||||
|
|
||||||
struct FileArtifactAdapter;
|
struct FileArtifactAdapter;
|
||||||
|
|
||||||
|
|
@ -59,6 +61,60 @@ fn adapter_capabilities_can_report_exact_resolution_only() {
|
||||||
assert!(!capabilities.supports_search);
|
assert!(!capabilities.supports_search);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn appimagehub_adapter_reports_search_and_exact_resolution_capabilities() {
|
||||||
|
let adapter = AppImageHubAdapter;
|
||||||
|
|
||||||
|
assert_eq!(adapter.id(), "appimagehub");
|
||||||
|
assert_eq!(
|
||||||
|
adapter.repository_source_kind(),
|
||||||
|
Some(SourceKind::AppImageHub)
|
||||||
|
);
|
||||||
|
assert_eq!(adapter.exact_source_kind(), None);
|
||||||
|
assert_eq!(
|
||||||
|
adapter.capabilities(),
|
||||||
|
AdapterCapabilities {
|
||||||
|
supports_search: true,
|
||||||
|
supports_exact_resolution: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn appimagehub_adapter_resolves_installable_items_through_fixture_transport() {
|
||||||
|
let adapter = AppImageHubAdapter;
|
||||||
|
let source = resolve_query("appimagehub/2338455").unwrap();
|
||||||
|
|
||||||
|
let resolution = adapter
|
||||||
|
.resolve_source_with(&source, &FixtureAppImageHubTransport)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
resolution,
|
||||||
|
AdapterResolveOutcome::Resolved(AdapterResolution {
|
||||||
|
source,
|
||||||
|
release: ResolvedRelease { version, .. },
|
||||||
|
}) if source.kind == SourceKind::AppImageHub
|
||||||
|
&& source.canonical_locator.as_deref() == Some("2338455")
|
||||||
|
&& version == "latest"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn appimagehub_adapter_reports_no_installable_artifact_for_non_appimage_items() {
|
||||||
|
let adapter = AppImageHubAdapter;
|
||||||
|
let source = resolve_query("appimagehub/2337998").unwrap();
|
||||||
|
|
||||||
|
let resolution = adapter
|
||||||
|
.resolve_source_with(&source, &FixtureAppImageHubTransport)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolution,
|
||||||
|
AdapterResolveOutcome::NoInstallableArtifact { source }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repository_backed_resolvers_accept_only_their_own_source_kind() {
|
fn repository_backed_resolvers_accept_only_their_own_source_kind() {
|
||||||
let github_source = resolve_query("sharkdp/bat").unwrap();
|
let github_source = resolve_query("sharkdp/bat").unwrap();
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use upm_core::adapters::all_adapter_kinds;
|
use aim_core::adapters::all_adapter_kinds;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn all_expected_adapter_kinds_are_registered() {
|
fn all_expected_adapter_kinds_are_registered() {
|
||||||
let kinds = all_adapter_kinds();
|
let kinds = all_adapter_kinds();
|
||||||
|
|
||||||
|
assert!(kinds.contains(&"appimagehub"));
|
||||||
assert!(kinds.contains(&"github"));
|
assert!(kinds.contains(&"github"));
|
||||||
assert!(kinds.contains(&"gitlab"));
|
assert!(kinds.contains(&"gitlab"));
|
||||||
assert!(kinds.contains(&"direct-url"));
|
assert!(kinds.contains(&"direct-url"));
|
||||||
assert!(kinds.contains(&"zsync"));
|
assert!(kinds.contains(&"zsync"));
|
||||||
assert!(kinds.contains(&"sourceforge"));
|
assert!(kinds.contains(&"sourceforge"));
|
||||||
assert!(!kinds.contains(&"appimagehub"));
|
|
||||||
assert!(!kinds.contains(&"custom-json"));
|
assert!(!kinds.contains(&"custom-json"));
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,12 @@
|
||||||
use upm_appimage::add::{AppImageHubAdapter, AppImageHubAddProvider};
|
use aim_core::app::search::{
|
||||||
use upm_appimage::search::AppImageHubSearchProvider;
|
AppImageHubSearchProvider, GitHubSearchProvider, SearchProvider, SearchProviderError,
|
||||||
use upm_appimage::source::appimagehub::FixtureAppImageHubTransport;
|
build_search_results_with,
|
||||||
use upm_core::adapters::traits::AdapterResolveOutcome;
|
|
||||||
use upm_core::app::providers::ExternalAddProvider;
|
|
||||||
use upm_core::app::query::resolve_query;
|
|
||||||
use upm_core::app::search::{
|
|
||||||
GitHubSearchProvider, SearchProvider, SearchProviderError, build_search_results_with,
|
|
||||||
};
|
};
|
||||||
use upm_core::domain::app::AppRecord;
|
use aim_core::domain::app::AppRecord;
|
||||||
use upm_core::domain::search::{SearchInstallStatus, SearchQuery, SearchResult};
|
use aim_core::domain::search::{SearchInstallStatus, SearchQuery, SearchResult};
|
||||||
use upm_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind, SourceRef};
|
use aim_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceKind, SourceRef};
|
||||||
use upm_core::source::github::FixtureGitHubTransport;
|
use aim_core::source::appimagehub::FixtureAppImageHubTransport;
|
||||||
|
use aim_core::source::github::FixtureGitHubTransport;
|
||||||
|
|
||||||
struct StubProvider {
|
struct StubProvider {
|
||||||
hit: SearchResult,
|
hit: SearchResult,
|
||||||
|
|
@ -24,7 +20,7 @@ impl SearchProvider for StubProvider {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn appimagehub_search_provider_maps_hits_to_install_ready_results() {
|
fn appimagehub_search_provider_maps_hits_to_install_ready_results() {
|
||||||
let provider = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
|
let provider = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
|
||||||
|
|
||||||
let results = provider.search(&SearchQuery::new("firefox")).unwrap();
|
let results = provider.search(&SearchQuery::new("firefox")).unwrap();
|
||||||
|
|
||||||
|
|
@ -38,7 +34,7 @@ fn appimagehub_search_provider_maps_hits_to_install_ready_results() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn appimagehub_hits_are_annotated_as_installed_by_canonical_id() {
|
fn appimagehub_hits_are_annotated_as_installed_by_canonical_id() {
|
||||||
let provider = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
|
let provider = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
|
||||||
let installed = vec![AppRecord {
|
let installed = vec![AppRecord {
|
||||||
stable_id: "firefox".to_owned(),
|
stable_id: "firefox".to_owned(),
|
||||||
display_name: "Firefox by Mozilla - Official AppImage Edition".to_owned(),
|
display_name: "Firefox by Mozilla - Official AppImage Edition".to_owned(),
|
||||||
|
|
@ -76,7 +72,7 @@ fn appimagehub_hits_are_annotated_as_installed_by_canonical_id() {
|
||||||
#[test]
|
#[test]
|
||||||
fn search_can_merge_github_and_appimagehub_providers() {
|
fn search_can_merge_github_and_appimagehub_providers() {
|
||||||
let github = GitHubSearchProvider::new(&FixtureGitHubTransport);
|
let github = GitHubSearchProvider::new(&FixtureGitHubTransport);
|
||||||
let appimagehub = AppImageHubSearchProvider::new(Box::new(FixtureAppImageHubTransport));
|
let appimagehub = AppImageHubSearchProvider::new(&FixtureAppImageHubTransport);
|
||||||
let stub = StubProvider {
|
let stub = StubProvider {
|
||||||
hit: SearchResult {
|
hit: SearchResult {
|
||||||
provider_id: "github".to_owned(),
|
provider_id: "github".to_owned(),
|
||||||
|
|
@ -110,40 +106,3 @@ fn search_can_merge_github_and_appimagehub_providers() {
|
||||||
.any(|hit| hit.provider_id == "appimagehub")
|
.any(|hit| hit.provider_id == "appimagehub")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn appimagehub_adapter_resolves_installable_items_through_fixture_transport() {
|
|
||||||
let adapter = AppImageHubAdapter;
|
|
||||||
let source = resolve_query("appimagehub/2338455").unwrap();
|
|
||||||
|
|
||||||
let resolution = adapter
|
|
||||||
.resolve_source_with(&source, &FixtureAppImageHubTransport)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
resolution,
|
|
||||||
AdapterResolveOutcome::Resolved(resolution)
|
|
||||||
if resolution.source.kind == SourceKind::AppImageHub
|
|
||||||
&& resolution.source.canonical_locator.as_deref() == Some("2338455")
|
|
||||||
&& resolution.release.version == "latest"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn appimagehub_add_provider_resolves_external_add_plan() {
|
|
||||||
let provider = AppImageHubAddProvider::new(Box::new(FixtureAppImageHubTransport));
|
|
||||||
let source = resolve_query("appimagehub/2338455").unwrap();
|
|
||||||
|
|
||||||
let resolution = provider.resolve(&source).unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(resolution.resolution.source.kind, SourceKind::AppImageHub);
|
|
||||||
assert_eq!(resolution.resolution.release.version, "latest");
|
|
||||||
assert_eq!(
|
|
||||||
resolution.selected_artifact.url,
|
|
||||||
"https://files06.pling.com/api/files/download/firefox-x86-64.AppImage"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
resolution.display_name_hint.as_deref(),
|
|
||||||
Some("Firefox by Mozilla - Official AppImage Edition")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
use aim_core::integration::install::{InstallRequest, PayloadInstallError, execute_install};
|
||||||
|
use aim_core::platform::DesktopHelpers;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use upm_core::integration::install::{InstallRequest, PayloadInstallError, execute_install};
|
|
||||||
use upm_core::platform::DesktopHelpers;
|
|
||||||
|
|
||||||
const VALID_FIXTURE_SHA512: &str =
|
const VALID_FIXTURE_SHA512: &str =
|
||||||
"ZZma4ZD+9XB4GGTHCNZu8I92OY02YrEvIG89ZtRNi99W8SZKwWkmGZz/QyNBxqAt0XeiKtcR80/dMnKlwpcIWw==";
|
"ZZma4ZD+9XB4GGTHCNZu8I92OY02YrEvIG89ZtRNi99W8SZKwWkmGZz/QyNBxqAt0XeiKtcR80/dMnKlwpcIWw==";
|
||||||
|
|
@ -2,15 +2,15 @@ use std::fs;
|
||||||
use std::io::{self, Cursor, Read};
|
use std::io::{self, Cursor, Read};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use tempfile::tempdir;
|
use aim_core::app::add::{
|
||||||
use upm_core::app::add::{
|
|
||||||
InstallAppError, download_to_staged_path_with_retries,
|
InstallAppError, download_to_staged_path_with_retries,
|
||||||
stream_payload_to_staged_file_with_reporter,
|
stream_payload_to_staged_file_with_reporter,
|
||||||
};
|
};
|
||||||
use upm_core::app::progress::{NoopReporter, OperationEvent};
|
use aim_core::app::progress::{NoopReporter, OperationEvent};
|
||||||
use upm_core::integration::install::{InstallRequest, execute_install};
|
use aim_core::integration::install::{InstallRequest, execute_install};
|
||||||
use upm_core::platform::DesktopHelpers;
|
use aim_core::platform::DesktopHelpers;
|
||||||
use upm_core::source::github::HttpClientPolicy;
|
use aim_core::source::github::HttpClientPolicy;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn payload_streaming_writes_staged_file_and_reports_progress() {
|
fn payload_streaming_writes_staged_file_and_reports_progress() {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use upm_core::app::add::{build_add_plan_with, materialize_app_record, prefer_latest_tracking};
|
use aim_core::app::add::{build_add_plan_with, materialize_app_record, prefer_latest_tracking};
|
||||||
use upm_core::app::query::resolve_query;
|
use aim_core::app::query::resolve_query;
|
||||||
use upm_core::source::github::FixtureGitHubTransport;
|
use aim_core::source::github::FixtureGitHubTransport;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn github_adapter_can_normalize_owner_repo_source() {
|
fn github_adapter_can_normalize_owner_repo_source() {
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::time::Duration;
|
use aim_core::app::query::resolve_query;
|
||||||
use upm_core::app::query::resolve_query;
|
use aim_core::source::github::{
|
||||||
use upm_core::source::github::{
|
|
||||||
FixtureGitHubTransport, discover_github_candidates_with, http_client_policy,
|
FixtureGitHubTransport, discover_github_candidates_with, http_client_policy,
|
||||||
};
|
};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discovery_reports_appimage_assets_and_latest_linux_yml() {
|
fn discovery_reports_appimage_assets_and_latest_linux_yml() {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use upm_core::app::identity::{IdentityFallback, resolve_identity};
|
use aim_core::app::identity::{IdentityFallback, resolve_identity};
|
||||||
use upm_core::domain::app::IdentityConfidence;
|
use aim_core::domain::app::IdentityConfidence;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unresolved_identity_can_fall_back_to_url() {
|
fn unresolved_identity_can_fall_back_to_url() {
|
||||||
|
|
@ -42,6 +42,6 @@ fn identifiers_containing_dot_dot_are_rejected() {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error,
|
error,
|
||||||
upm_core::app::identity::ResolveIdentityError::InvalidStableId
|
aim_core::app::identity::ResolveIdentityError::InvalidStableId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
|
use aim_core::app::add::{BuildAddPlanError, build_add_plan_with};
|
||||||
|
use aim_core::app::query::ResolveQueryError;
|
||||||
|
use aim_core::app::update::execute_updates;
|
||||||
|
use aim_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
||||||
|
use aim_core::domain::source::SourceKind;
|
||||||
|
use aim_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceRef};
|
||||||
|
use aim_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
||||||
|
use aim_core::platform::DesktopHelpers;
|
||||||
|
use aim_core::source::github::FixtureGitHubTransport;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use upm_core::app::add::{BuildAddPlanError, build_add_plan_with};
|
|
||||||
use upm_core::app::query::ResolveQueryError;
|
|
||||||
use upm_core::app::update::execute_updates;
|
|
||||||
use upm_core::domain::app::{AppRecord, InstallMetadata, InstallScope};
|
|
||||||
use upm_core::domain::source::SourceKind;
|
|
||||||
use upm_core::domain::source::{NormalizedSourceKind, SourceInputKind, SourceRef};
|
|
||||||
use upm_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
|
||||||
use upm_core::platform::DesktopHelpers;
|
|
||||||
use upm_core::source::github::FixtureGitHubTransport;
|
|
||||||
|
|
||||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ fn integration_failure_removes_new_payload_and_generated_files() {
|
||||||
fs::write(&staged_path, b"\x7fELFAppImage").unwrap();
|
fs::write(&staged_path, b"\x7fELFAppImage").unwrap();
|
||||||
|
|
||||||
let final_payload_path = payload_root.join("bat.AppImage");
|
let final_payload_path = payload_root.join("bat.AppImage");
|
||||||
let desktop_entry_path = blocking_path.join("upm-bat.desktop");
|
let desktop_entry_path = blocking_path.join("aim-bat.desktop");
|
||||||
let error = execute_install(&InstallRequest {
|
let error = execute_install(&InstallRequest {
|
||||||
staged_payload_path: &staged_path,
|
staged_payload_path: &staged_path,
|
||||||
final_payload_path: &final_payload_path,
|
final_payload_path: &final_payload_path,
|
||||||
|
|
@ -85,13 +85,13 @@ fn failed_update_restores_tracked_desktop_and_icon_files() {
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
std::env::set_var("DISPLAY", ":99");
|
std::env::set_var("DISPLAY", ":99");
|
||||||
std::env::set_var("XDG_CURRENT_DESKTOP", "test");
|
std::env::set_var("XDG_CURRENT_DESKTOP", "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload_path = root.path().join("tracked/team-app.AppImage");
|
let payload_path = root.path().join("tracked/team-app.AppImage");
|
||||||
let desktop_path = root.path().join("tracked/upm-team-app.desktop");
|
let desktop_path = root.path().join("tracked/aim-team-app.desktop");
|
||||||
let icon_path = root.path().join("tracked/team-app.png");
|
let icon_path = root.path().join("tracked/team-app.png");
|
||||||
fs::create_dir_all(payload_path.parent().unwrap()).unwrap();
|
fs::create_dir_all(payload_path.parent().unwrap()).unwrap();
|
||||||
fs::write(&payload_path, b"previous-payload").unwrap();
|
fs::write(&payload_path, b"previous-payload").unwrap();
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
|
use aim_core::app::add::{build_add_plan_with_reporter, install_app_with_reporter};
|
||||||
|
use aim_core::app::progress::{OperationEvent, OperationStage};
|
||||||
|
use aim_core::domain::app::InstallScope;
|
||||||
|
use aim_core::domain::source::{NormalizedSourceKind, SourceKind};
|
||||||
|
use aim_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
||||||
|
use aim_core::platform::DesktopHelpers;
|
||||||
|
use aim_core::source::github::FixtureGitHubTransport;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use upm_core::app::add::{build_add_plan_with_reporter, install_app_with_reporter};
|
|
||||||
use upm_core::app::progress::{OperationEvent, OperationStage};
|
|
||||||
use upm_core::domain::app::InstallScope;
|
|
||||||
use upm_core::domain::source::{NormalizedSourceKind, SourceKind};
|
|
||||||
use upm_core::integration::install::{DesktopIntegrationRequest, InstallRequest, execute_install};
|
|
||||||
use upm_core::platform::DesktopHelpers;
|
|
||||||
use upm_core::source::github::FixtureGitHubTransport;
|
|
||||||
|
|
||||||
fn write_staged_payload(root: &std::path::Path, name: &str, bytes: &[u8]) -> std::path::PathBuf {
|
fn write_staged_payload(root: &std::path::Path, name: &str, bytes: &[u8]) -> std::path::PathBuf {
|
||||||
let staged_path = root.join("staging").join(format!("{name}.download"));
|
let staged_path = root.join("staging").join(format!("{name}.download"));
|
||||||
|
|
@ -32,7 +32,7 @@ fn install_writes_desktop_entry_and_reports_refresh_warning_only() {
|
||||||
trusted_checksum: None,
|
trusted_checksum: None,
|
||||||
weak_checksum_md5: None,
|
weak_checksum_md5: None,
|
||||||
desktop: Some(DesktopIntegrationRequest {
|
desktop: Some(DesktopIntegrationRequest {
|
||||||
desktop_entry_path: &desktop_root.join("upm-bat.desktop"),
|
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||||
icon_path: None,
|
icon_path: None,
|
||||||
icon_bytes: None,
|
icon_bytes: None,
|
||||||
|
|
@ -86,7 +86,7 @@ fn install_executes_refresh_helpers_when_available() {
|
||||||
trusted_checksum: None,
|
trusted_checksum: None,
|
||||||
weak_checksum_md5: None,
|
weak_checksum_md5: None,
|
||||||
desktop: Some(DesktopIntegrationRequest {
|
desktop: Some(DesktopIntegrationRequest {
|
||||||
desktop_entry_path: &desktop_root.join("upm-bat.desktop"),
|
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||||
icon_path: Some(&icon_root.join("bat.png")),
|
icon_path: Some(&icon_root.join("bat.png")),
|
||||||
icon_bytes: None,
|
icon_bytes: None,
|
||||||
|
|
@ -128,7 +128,7 @@ fn install_extracts_icon_from_appimage_payload_when_icon_path_is_requested() {
|
||||||
trusted_checksum: None,
|
trusted_checksum: None,
|
||||||
weak_checksum_md5: None,
|
weak_checksum_md5: None,
|
||||||
desktop: Some(DesktopIntegrationRequest {
|
desktop: Some(DesktopIntegrationRequest {
|
||||||
desktop_entry_path: &desktop_root.join("upm-bat.desktop"),
|
desktop_entry_path: &desktop_root.join("aim-bat.desktop"),
|
||||||
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
desktop_entry_contents: "[Desktop Entry]\nName=bat\nExec=bat.AppImage\nType=Application\n",
|
||||||
icon_path: Some(&icon_root.join("bat.png")),
|
icon_path: Some(&icon_root.join("bat.png")),
|
||||||
icon_bytes: None,
|
icon_bytes: None,
|
||||||
|
|
@ -152,7 +152,7 @@ fn install_app_reports_operation_stages_in_order() {
|
||||||
let mut events: Vec<OperationEvent> = Vec::new();
|
let mut events: Vec<OperationEvent> = Vec::new();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |event: &OperationEvent| events.push(event.clone());
|
let mut reporter = |event: &OperationEvent| events.push(event.clone());
|
||||||
|
|
@ -249,7 +249,7 @@ fn install_app_sanitizes_desktop_entry_display_names() {
|
||||||
let mut reporter = Vec::new();
|
let mut reporter = Vec::new();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut capture = |event: &OperationEvent| reporter.push(event.clone());
|
let mut capture = |event: &OperationEvent| reporter.push(event.clone());
|
||||||
|
|
@ -349,7 +349,7 @@ fn gitlab_install_preserves_truthful_gitlab_origin() {
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |_event: &OperationEvent| {};
|
let mut reporter = |_event: &OperationEvent| {};
|
||||||
|
|
@ -399,7 +399,7 @@ fn direct_url_install_preserves_truthful_direct_url_origin() {
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |_event: &OperationEvent| {};
|
let mut reporter = |_event: &OperationEvent| {};
|
||||||
|
|
@ -484,7 +484,7 @@ fn sourceforge_latest_download_install_preserves_truthful_origin() {
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |_event: &OperationEvent| {};
|
let mut reporter = |_event: &OperationEvent| {};
|
||||||
|
|
@ -514,7 +514,7 @@ fn sourceforge_release_folder_install_preserves_truthful_origin() {
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |_event: &OperationEvent| {};
|
let mut reporter = |_event: &OperationEvent| {};
|
||||||
|
|
@ -577,7 +577,7 @@ fn sourceforge_file_like_release_download_install_preserves_input_but_stores_rel
|
||||||
let root = tempdir().unwrap();
|
let root = tempdir().unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
std::env::set_var("UPM_GITHUB_FIXTURE_MODE", "1");
|
std::env::set_var("AIM_GITHUB_FIXTURE_MODE", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reporter = |_event: &OperationEvent| {};
|
let mut reporter = |_event: &OperationEvent| {};
|
||||||
21
crates/aim-core/tests/install_paths.rs
Normal file
21
crates/aim-core/tests/install_paths.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use aim_core::domain::app::InstallScope;
|
||||||
|
use aim_core::integration::paths::{desktop_entry_path, managed_appimage_path};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn user_scope_path_lands_under_home_managed_dir() {
|
||||||
|
let path = managed_appimage_path(Path::new("/home/test"), InstallScope::User, "bat");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
path,
|
||||||
|
Path::new("/home/test/.local/lib/aim/appimages/bat.AppImage")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_scope_desktop_entry_uses_system_prefix() {
|
||||||
|
let path = desktop_entry_path(Path::new("/home/test"), InstallScope::System, "bat");
|
||||||
|
|
||||||
|
assert_eq!(path, Path::new("/usr/share/applications/aim-bat.desktop"));
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
use aim_core::integration::install::stage_and_commit_payload;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use upm_core::integration::install::stage_and_commit_payload;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn payload_commit_moves_staged_appimage_into_final_location() {
|
fn payload_commit_moves_staged_appimage_into_final_location() {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use aim_core::integration::policy::{IntegrationMode, resolve_install_policy};
|
||||||
|
use aim_core::platform::{DistroFamily, HostCapabilities, InstallScope};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use upm_core::integration::policy::{IntegrationMode, resolve_install_policy};
|
|
||||||
use upm_core::platform::{DistroFamily, HostCapabilities, InstallScope};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn immutable_system_request_downgrades_to_user_when_allowed() {
|
fn immutable_system_request_downgrades_to_user_when_allowed() {
|
||||||
|
|
@ -36,7 +36,7 @@ fn system_policy_uses_managed_payload_and_native_integration_roots() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(policy.scope, InstallScope::System);
|
assert_eq!(policy.scope, InstallScope::System);
|
||||||
assert_eq!(policy.payload_root, Path::new("/opt/upm/appimages"));
|
assert_eq!(policy.payload_root, Path::new("/opt/aim/appimages"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
policy.desktop_entry_root,
|
policy.desktop_entry_root,
|
||||||
Path::new("/usr/share/applications")
|
Path::new("/usr/share/applications")
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use upm_core::app::scope::{ScopeOverride, resolve_install_scope};
|
use aim_core::app::scope::{ScopeOverride, resolve_install_scope};
|
||||||
use upm_core::domain::app::InstallScope;
|
use aim_core::domain::app::InstallScope;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn explicit_scope_override_beats_effective_user() {
|
fn explicit_scope_override_beats_effective_user() {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use upm_core::domain::update::ParsedMetadataKind;
|
use aim_core::domain::update::ParsedMetadataKind;
|
||||||
use upm_core::metadata::{MetadataDocument, parse_document};
|
use aim_core::metadata::{MetadataDocument, parse_document};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_document_returns_typed_warning_not_panic() {
|
fn unknown_document_returns_typed_warning_not_panic() {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use upm_core::domain::update::ParsedMetadataKind;
|
use aim_core::domain::update::ParsedMetadataKind;
|
||||||
use upm_core::metadata::{MetadataDocument, parse_document};
|
use aim_core::metadata::{MetadataDocument, parse_document};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_latest_linux_yml_into_download_hints() {
|
fn parses_latest_linux_yml_into_download_hints() {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use upm_core::domain::update::ParsedMetadataKind;
|
use aim_core::domain::update::ParsedMetadataKind;
|
||||||
use upm_core::metadata::{MetadataDocument, parse_document};
|
use aim_core::metadata::{MetadataDocument, parse_document};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_zsync_document_into_channel_hints() {
|
fn parses_zsync_document_into_channel_hints() {
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue