Skip to content
Open
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
1 change: 0 additions & 1 deletion src/anolisa/crates/anolisa-cli/src/repo_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,6 @@ pub fn raw_index_v2_url(base_url: &str) -> String {
///
/// Released clients continue reading this path, so publishers must preserve its
/// schema v1 contents when publishing later component-index generations.
#[cfg(test)]
pub fn component_index_url(base_url: &str) -> String {
format!("{}/components.toml", raw_root(base_url))
}
Expand Down
70 changes: 51 additions & 19 deletions src/anolisa/crates/anolisa-cli/src/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use anolisa_platform::pkg_query::{PackageQuery, PackageQueryError};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::repo_config::{BackendConfig, HostVars, RepoConfig, component_index_v2_url};
use crate::repo_config::{
BackendConfig, HostVars, RepoConfig, component_index_url, component_index_v2_url,
};

/// On-disk schema version for repo-side `components-v2.toml`.
pub(crate) const COMPONENT_INDEX_SCHEMA_VERSION: u32 = 2;
Expand Down Expand Up @@ -54,6 +56,7 @@ pub(crate) struct ComponentIndexEntry {
#[serde(default)]
pub(crate) summary: Option<String>,
/// Supported host OS/architecture combinations.
#[serde(default)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Populate targets when accepting v1 indexes

When the fallback loads a schema v1 components.toml, existing v1 rows use platforms = [...] rather than targets, so defaulting targets to an empty list makes supports_target() false for every component. In particular, install --all filters through component_names_for_target() and will report nothing to install even though the fallback index was loaded; translate v1 platform data into target availability or avoid target filtering for schema v1.

Useful? React with 👍 / 👎.

pub(crate) targets: Vec<ComponentTarget>,
/// Backend-native package names for this component.
///
Expand Down Expand Up @@ -473,7 +476,11 @@ impl ComponentIndex {
}

fn validate(&self) -> Result<(), ComponentIndexError> {
if self.schema_version != COMPONENT_INDEX_SCHEMA_VERSION {
// Accept schema v1 and v2: v1 rows simply lack `targets`, which
// defaults to an empty vec via `#[serde(default)]`. This lets the
// v2 parser gracefully load v1 `components.toml` when the v2 file
// has not been published to the mirror yet.
if !matches!(self.schema_version, 1 | 2) {
return Err(ComponentIndexError::UnsupportedSchema {
actual: self.schema_version,
expected: COMPONENT_INDEX_SCHEMA_VERSION,
Expand All @@ -492,7 +499,9 @@ impl ComponentIndex {
reason: format!("duplicate component '{name}'"),
});
}
if entry.targets.is_empty() {
// v1 indexes predate the `targets` field; skip the non-empty
// check for schema v1 (targets defaults to empty via serde).
if self.schema_version >= 2 && entry.targets.is_empty() {
return Err(ComponentIndexError::Invalid {
reason: format!("component '{name}' must declare at least one target"),
});
Expand Down Expand Up @@ -741,18 +750,39 @@ pub(crate) fn load_component_index(
let url = component_index_v2_url(&base_url);

let cache = DownloadCache::new(layout.cache_dir.clone());
let downloaded = match fetch_index_url(&cache, &url) {
Ok(art) => art,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P1] v1 回退逻辑依赖错误文案匹配,易受服务端文案变动影响

当前 v2→v1 回退通过 reason.contains("http status 404") 识别 404,resolution.rs:752-755。一旦底层 download 层调整错误信息格式(如本地化、移除 http status 前缀),v1 回退会静默失效,重新暴露 404 硬失败。建议显式携带/匹配 HTTP 状态码(或定义结构化错误枚举),在 404 时触发回退,避免对 free-text 文案的脆弱依赖。


🤖 Generated by QoderFix in Qoder

Err(ComponentIndexError::Fetch { ref reason }) if reason.contains("http status 404") => {
// v2 not published yet — fall back to v1 components.toml.
// The v2 parser accepts v1 rows because `targets` has
// `#[serde(default)]`, so legacy indexes without targets
// still load (components simply appear as "no target info").
let v1_url = component_index_url(&base_url);
fetch_index_url(&cache, &v1_url).map_err(|err| ComponentIndexError::Fetch {
reason: format!("failed to fetch component index (tried v2 then v1): {err}"),
})?
}
Err(err) => return Err(err),
};
ComponentIndex::load(&downloaded.cached_path)
}

/// Fetch a component index TOML from `url` into `cache`.
fn fetch_index_url(
cache: &DownloadCache,
url: &str,
) -> Result<anolisa_core::download::DownloadedArtifact, ComponentIndexError> {
#[cfg(test)]
if !url.starts_with("file://") {
return Err(ComponentIndexError::Fetch {
reason: format!("test mode: refusing non-file URL {url}"),
});
}
let downloaded = cache
.fetch(&url, None)
cache
.fetch(url, None)
.map_err(|err| ComponentIndexError::Fetch {
reason: format!("failed to fetch {url}: {err}"),
})?;
ComponentIndex::load(&downloaded.cached_path)
})
}

/// Best-effort load of repo-side `components-v2.toml`.
Expand Down Expand Up @@ -1325,17 +1355,19 @@ cosh = "site-copilot"

#[test]
fn unsupported_schema_is_rejected() {
for actual in [1, 99] {
let source = format!("schema_version = {actual}");
let err = ComponentIndex::from_toml_str(&source, "components.toml")
.expect_err("unsupported schema");
assert!(matches!(
err,
ComponentIndexError::UnsupportedSchema {
actual: rejected,
..
} if rejected == actual
));
}
// Schema v1 and v2 are accepted; anything else is rejected.
// v1 is accepted because `targets` defaults to an empty vec and
// the non-empty-target check is skipped for schema v1.
let actual = 99;
let source = format!("schema_version = {actual}");
let err = ComponentIndex::from_toml_str(&source, "components.toml")
.expect_err("unsupported schema");
assert!(matches!(
err,
ComponentIndexError::UnsupportedSchema {
actual: rejected,
..
} if rejected == actual
));
}
}
Loading