Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: CI

# A newer push to the same PR obsoletes the running build: cancel it.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:
branches: [main]
Expand All @@ -25,9 +30,6 @@ jobs:

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

- name: Check
run: cargo check --verbose

- name: Format
run: cargo fmt --all -- --check

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
name = "colony"
version = "0.8.0"
edition = "2021"
# Enforced floor for the README's "Rust 1.80+" claim (std::sync::LazyLock).
rust-version = "1.80"

[dependencies]
anyhow = "1.0.100"
Expand Down
24 changes: 17 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ src/
├── update.rs — Handlers for each Message variant
├── github.rs — GitHub API, ETag cache, manifests, release
│ asset resolution, platform auto-detection
├── download.rs — Asset/archive downloads, extraction, self-update
├── signing.rs — ed25519 verification of signed launcher updates
├── persistence.rs — Data dirs, install state, on-disk caches, favorites
├── download.rs — Asset/archive downloads, extraction, app-signature
│ verification, self-update
├── signing.rs — ed25519 verification (launcher AND app releases)
├── icons.rs — PNG decoding for per-app grid icons
├── persistence.rs — Data dirs, install state, on-disk caches, favorites,
│ desktop entries (Linux)
├── config.rs — Locating external config (categories.json, colony.toml)
├── oauth.rs — Device Flow OAuth (login, token, keychain)
├── scan.rs — System application scanning (Linux/Windows/macOS)
Expand All @@ -45,7 +48,9 @@ src/
├── app_grid.rs — Application card grid with search
├── detail.rs — Detail view (README, changelog, license, actions)
├── settings.rs — Settings panel (theme, language, about, updates)
└── github_panel.rs — GitHub connect/disconnect, Device Flow UI
├── github_panel.rs — GitHub connect/disconnect, Device Flow UI
├── markdown_blocks.rs — Cached-block Markdown rendering
└── tutorial.rs — First-launch guided tour (spotlight overlay)
```

## Data flow (Elm architecture)
Expand Down Expand Up @@ -94,13 +99,14 @@ All async operations (API calls, downloads, scanning) return a `Task<Message>` t

1. Compares `CARGO_PKG_VERSION` vs latest release from `Project-Colony/Colony`
2. Downloads the platform-specific binary to `update-staging/`
3. Replacement sequence: backup to `.old` → copy new binary → chmod 755
3. Replacement sequence: backup to `.old` → write the signature-verified
bytes (never a re-read of the staged file) → chmod 755
4. Automatic rollback if copy fails
5. Spawns the new binary → exits the old one

## Tests

62 unit tests covering:
105 unit tests covering:
- `colony.json` manifest parsing (full, minimal, with pattern, with archives)
- Platform auto-detection from release assets
- `release_files` construction from assets
Expand All @@ -109,5 +115,9 @@ All async operations (API calls, downloads, scanning) return a `Task<Message>` t
- Environment variable expansion
- Application categorization
- Section filters
- Localization (EN/FR)
- Localization (EN/FR, key parity between languages)
- Preferences serialization
- Update-loop state transitions (catalog refresh, update queue, badges,
launcher-check outcomes, cancel semantics) via a hermetic test App
- filePattern globs with exclusions, signature parsing (strict ed25519),
typed HTTP-status classification
65 changes: 65 additions & 0 deletions src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,71 @@ mod tests {
);
}

#[test]
fn spec_conformant_manifest_parses_field_for_field() {
// Locks docs/colony-spec.md <-> code parity: this sample uses every
// documented manifest field with the spec's exact camelCase names.
// If a rename or removal breaks the spec, this test fails first.
let json = r#"{
"name": "Lilypad",
"category": "Security",
"platforms": ["windows", "linux", "macos", "macos-x86"],
"icon": "assets/icons/icon.png",
"signed": true,
"releaseFiles": {
"linux": {
"tag": "latest",
"filePattern": "lilypad-*-linux.tar.gz, !*-arm64*",
"binary": "lilypad-cli",
"sha256": "abc123"
},
"windows": {
"tag": "v1.0.0",
"file": "lilypad-windows.zip",
"binary": "lilypad-cli.exe"
}
}
}"#;
let m: ColonyManifest = serde_json::from_str(json).expect("spec sample must parse");
assert_eq!(m.name, "Lilypad");
assert_eq!(m.category, "Security");
assert_eq!(m.platforms.len(), 4);
assert_eq!(m.icon.as_deref(), Some("assets/icons/icon.png"));
assert!(m.signed);
let linux = &m.release_files["linux"];
assert_eq!(linux.tag, "latest");
assert_eq!(
linux.file_pattern.as_deref(),
Some("lilypad-*-linux.tar.gz, !*-arm64*")
);
assert_eq!(linux.binary.as_deref(), Some("lilypad-cli"));
assert_eq!(linux.sha256.as_deref(), Some("abc123"));
let windows = &m.release_files["windows"];
assert_eq!(windows.tag, "v1.0.0");
assert_eq!(windows.file.as_deref(), Some("lilypad-windows.zip"));
// Every spec category value (and its documented aliases) maps to a
// real category - never silently to Other (except Other itself).
for cat in [
"Development",
"Graphics",
"Network",
"Office",
"Multimedia",
"System",
"Utility",
"Utilities",
"Security",
"Game",
"Games",
] {
assert_ne!(
crate::scan::AppCategory::from_name(cat),
crate::scan::AppCategory::Other,
"spec category '{cat}' must not fall back to Other"
);
}
}

#[test]
fn manifest_signed_flag_parses_and_defaults_off() {
let json = r#"{ "name": "App", "category": "Utility", "signed": true }"#;
Expand Down