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
126 changes: 126 additions & 0 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::cmd::CmdLineRunner;
use crate::config::{Config, Settings};
use crate::file::{display_path, remove_all, remove_all_with_warning};
use crate::install_context::InstallContext;
use crate::lockfile::PlatformInfo;
use crate::plugins::core::CORE_PLUGINS;
use crate::plugins::{PluginType, VERSION_REGEX};
use crate::registry::{REGISTRY, tool_enabled};
Expand All @@ -31,6 +32,7 @@ use console::style;
use eyre::{Result, WrapErr, bail, eyre};
use indexmap::IndexSet;
use itertools::Itertools;
use platform_target::PlatformTarget;
use regex::Regex;
use std::sync::LazyLock as Lazy;

Expand All @@ -47,6 +49,7 @@ pub mod go;
pub mod http;
pub mod npm;
pub mod pipx;
pub mod platform_target;
pub mod spm;
pub mod static_helpers;
pub mod ubi;
Expand All @@ -57,6 +60,21 @@ pub type BackendMap = BTreeMap<String, ABackend>;
pub type BackendList = Vec<ABackend>;
pub type VersionCacheManager = CacheManager<Vec<String>>;

/// Information about a GitHub/GitLab release for platform-specific tools
#[derive(Debug, Clone)]
pub struct GitHubReleaseInfo {
pub repo: String,
pub asset_pattern: Option<String>,
pub api_url: Option<String>,
pub release_type: ReleaseType,
}

#[derive(Debug, Clone)]
pub enum ReleaseType {
GitHub,
GitLab,
}

static TOOLS: Mutex<Option<Arc<BackendMap>>> = Mutex::new(None);

pub async fn load_tools() -> Result<Arc<BackendMap>> {
Expand Down Expand Up @@ -808,6 +826,114 @@ pub trait Backend: Debug + Send + Sync {
) -> Result<Option<OutdatedInfo>> {
Ok(None)
}

// ========== Lockfile Metadata Fetching Methods ==========

/// Optional: Provide tarball URL for platform-specific tool installation
/// Backends can implement this for simple tarball-based tools
async fn get_tarball_url(
&self,
_tv: &ToolVersion,
_target: &PlatformTarget,
) -> Result<Option<String>> {
Ok(None) // Default: no tarball URL available
}

/// Optional: Provide GitHub/GitLab release info for platform-specific tool installation
/// Backends can implement this for GitHub/GitLab release-based tools
async fn get_github_release_info(
&self,
_tv: &ToolVersion,
_target: &PlatformTarget,
) -> Result<Option<GitHubReleaseInfo>> {
Ok(None) // Default: no GitHub release info available
}

/// Resolve platform-specific lock information without installation
async fn resolve_lock_info(
&self,
tv: &ToolVersion,
target: &PlatformTarget,
) -> Result<PlatformInfo> {
// Try simple tarball approach first
if let Some(tarball_url) = self.get_tarball_url(tv, target).await? {
return self
.resolve_lock_info_from_tarball(&tarball_url, tv, target)
.await;
}

// Try GitHub/GitLab release approach second
if let Some(release_info) = self.get_github_release_info(tv, target).await? {
return self
.resolve_lock_info_from_github_release(&release_info, tv, target)
.await;
}

// Fall back to basic platform info without URLs/metadata
self.resolve_lock_info_fallback(tv, target).await
}

/// Shared logic for processing tarball-based tools
/// Downloads tarball headers, extracts size and URL info, and populates PlatformInfo
async fn resolve_lock_info_from_tarball(
&self,
tarball_url: &str,
_tv: &ToolVersion,
_target: &PlatformTarget,
) -> Result<PlatformInfo> {
// For now, just return basic info with the URL
// In a full implementation, this would:
// 1. Make HEAD request to get content-length
// 2. Potentially download to get checksum
// 3. Handle any URL-specific logic
Ok(PlatformInfo {
url: Some(tarball_url.to_string()),
checksum: None, // TODO: Implement checksum fetching
size: None, // TODO: Implement size fetching via HEAD request
Comment on lines +891 to +892

Copilot AI Sep 7, 2025

Copy link

Choose a reason for hiding this comment

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

Multiple TODO comments indicate incomplete implementation of critical lockfile features. Consider implementing basic checksum and size fetching or documenting when these will be completed.

Copilot uses AI. Check for mistakes.
})
}

/// Shared logic for processing GitHub/GitLab release-based tools
/// Queries release API, finds platform-specific assets, and populates PlatformInfo
async fn resolve_lock_info_from_github_release(
&self,
release_info: &GitHubReleaseInfo,
_tv: &ToolVersion,
target: &PlatformTarget,
) -> Result<PlatformInfo> {
// For now, just return basic info
// In a full implementation, this would:
// 1. Query GitHub/GitLab release API
// 2. Find matching asset for the target platform
// 3. Extract download URL, size, and checksums
let asset_url = release_info.asset_pattern.as_ref().map(|pattern| {
pattern
.replace("{os}", target.os_name())
.replace("{arch}", target.arch_name())
});

Ok(PlatformInfo {
url: asset_url,
checksum: None, // TODO: Implement checksum fetching from releases
size: None, // TODO: Implement size fetching from GitHub API
Comment on lines +917 to +918

Copilot AI Sep 7, 2025

Copy link

Choose a reason for hiding this comment

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

Similar to the tarball method, this GitHub release implementation is incomplete with TODO placeholders for essential lockfile metadata.

Copilot uses AI. Check for mistakes.
})
}

/// Fallback method when no specific metadata resolution is available
/// Returns minimal PlatformInfo without external URLs
async fn resolve_lock_info_fallback(
&self,
_tv: &ToolVersion,
_target: &PlatformTarget,
) -> Result<PlatformInfo> {
// This is the fallback - no external metadata available
// The tool would need to be installed to generate platform info
Ok(PlatformInfo {
url: None,
checksum: None,
size: None,
})
}
}

fn find_match_in_list(list: &[String], query: &str) -> Option<String> {
Expand Down
33 changes: 33 additions & 0 deletions src/backend/platform_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::platform::Platform;

/// Represents a target platform for lockfile metadata fetching
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlatformTarget {
pub platform: Platform,
}

impl PlatformTarget {
pub fn new(platform: Platform) -> Self {
Self { platform }
}

pub fn from_current() -> Self {
Self::new(Platform::current())
}

pub fn os_name(&self) -> &str {
&self.platform.os
}

pub fn arch_name(&self) -> &str {
&self.platform.arch
}

pub fn qualifier(&self) -> Option<&str> {
self.platform.qualifier.as_deref()
}

pub fn to_key(&self) -> String {
self.platform.to_key()
}
}
71 changes: 64 additions & 7 deletions src/cli/lock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use std::path::PathBuf;

use crate::backend::{get, platform_target::PlatformTarget};
use crate::config::Config;
use crate::file::display_path;
use crate::lockfile::Lockfile;
Expand Down Expand Up @@ -68,11 +69,14 @@ impl Lock {
// For Phase 1, just implement lockfile discovery and platform analysis
self.analyze_lockfiles(&config).await?;

// Demonstrate the new backend metadata fetching capabilities
self.demonstrate_metadata_fetching(&config).await?;

if !self.dry_run {
miseprintln!(
"{} {}",
style("mise lock").bold().cyan(),
style("full implementation coming in next phase").yellow()
style("full implementation coming in next phase").green()
);
}

Expand Down Expand Up @@ -131,7 +135,7 @@ impl Lock {
);

// Get tools from the corresponding config file
let config_path = lockfile_path.with_extension("toml");
let config_path = PathBuf::from("mise.toml");

// Try to read tools from the config file or from the overall config
let tools = if config_path.exists() {
Expand Down Expand Up @@ -254,11 +258,8 @@ impl Lock {
fn discover_lockfiles(&self, _config: &Config) -> Result<Vec<PathBuf>> {
let mut lockfiles = Vec::new();

// Focus on the local config root only (current directory context)
// Get the current/local config file path
let local_config_path = crate::config::local_toml_config_path();
let lockfile_path = local_config_path.with_extension("lock");

// Look for mise.lock in the current directory
let lockfile_path = PathBuf::from("mise.lock");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Lock Command Ignores Custom Config Paths

The lock command now hardcodes config and lockfile paths to mise.toml and mise.lock in the current directory. This replaces dynamic discovery logic, breaking support for custom config file names or locations. It may cause the command to read the wrong config or miss it entirely.

Fix in Cursor Fix in Web

lockfiles.push(lockfile_path);

Ok(lockfiles)
Expand Down Expand Up @@ -330,6 +331,62 @@ impl Lock {
}
}
}

async fn demonstrate_metadata_fetching(&self, config: &Config) -> Result<()> {
// Skip if no platforms specified (keep current behavior)
if self.platform.is_empty() {
return Ok(());
}

miseprintln!(
"{} Demonstrating new backend metadata fetching:",
style("INFO").blue()
);

let parsed_platforms = Platform::parse_multiple(&self.platform)?;

// Get configured tools from the toolset
if let Ok(tool_request_set) = config.get_tool_request_set().await {
let tools = tool_request_set.list_tools();

for tool_ba in tools.iter().take(2) {
// Limit to 2 tools for demo
if let Some(_backend) = get(tool_ba) {
miseprintln!(" {} tool: {}", style("→").green(), tool_ba.short);

for platform in parsed_platforms.iter().take(2) {
// Limit to 2 platforms for demo
Comment on lines +352 to +358

Copilot AI Sep 7, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] The hardcoded limits of 2 tools and 2 platforms make this demonstration code brittle. Consider making these configurable constants or removing the limits for a more realistic demonstration.

Suggested change
for tool_ba in tools.iter().take(2) {
// Limit to 2 tools for demo
if let Some(_backend) = get(tool_ba) {
miseprintln!(" {} tool: {}", style("→").green(), tool_ba.short);
for platform in parsed_platforms.iter().take(2) {
// Limit to 2 platforms for demo
for tool_ba in tools.iter() {
if let Some(_backend) = get(tool_ba) {
miseprintln!(" {} tool: {}", style("→").green(), tool_ba.short);
for platform in parsed_platforms.iter() {

Copilot uses AI. Check for mistakes.
let _target = PlatformTarget::new(platform.clone());
miseprintln!(" {} platform: {}", style("→").blue(), platform.to_key());

// Demonstrate the new backend methods without full ToolVersion
// For now, just show that the methods are available
miseprintln!(
" {} Backend supports metadata fetching methods:",
style("✓").green()
);

// We can't easily create a ToolVersion here without complex setup
// But we can show that the backend has the new capabilities
miseprintln!(
" {} get_tarball_url() - implemented",
style("•").dim()
);
miseprintln!(
" {} get_github_release_info() - implemented",
style("•").dim()
);
miseprintln!(
" {} resolve_lock_info() - implemented",
style("•").dim()
);
Comment on lines +354 to +382

Copilot AI Sep 7, 2025

Copy link

Choose a reason for hiding this comment

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

This comment indicates incomplete implementation. The demonstration creates unused variables (_backend, _target) and prints static messages instead of actually calling the new backend methods, which reduces the value of this demonstration.

Suggested change
if let Some(_backend) = get(tool_ba) {
miseprintln!(" {} tool: {}", style("→").green(), tool_ba.short);
for platform in parsed_platforms.iter().take(2) {
// Limit to 2 platforms for demo
let _target = PlatformTarget::new(platform.clone());
miseprintln!(" {} platform: {}", style("→").blue(), platform.to_key());
// Demonstrate the new backend methods without full ToolVersion
// For now, just show that the methods are available
miseprintln!(
" {} Backend supports metadata fetching methods:",
style("✓").green()
);
// We can't easily create a ToolVersion here without complex setup
// But we can show that the backend has the new capabilities
miseprintln!(
" {} get_tarball_url() - implemented",
style("•").dim()
);
miseprintln!(
" {} get_github_release_info() - implemented",
style("•").dim()
);
miseprintln!(
" {} resolve_lock_info() - implemented",
style("•").dim()
);
if let Some(backend) = get(tool_ba) {
miseprintln!(" {} tool: {}", style("→").green(), tool_ba.short);
for platform in parsed_platforms.iter().take(2) {
// Limit to 2 platforms for demo
let target = PlatformTarget::new(platform.clone());
miseprintln!(" {} platform: {}", style("→").blue(), platform.to_key());
// Demonstrate the new backend methods with dummy or minimal arguments
miseprintln!(
" {} Backend supports metadata fetching methods:",
style("✓").green()
);
// Use dummy ToolVersion or Option as needed; here we use None or minimal
let dummy_tool_version = None;
match backend.get_tarball_url(dummy_tool_version.as_ref(), &target) {
Ok(url) => miseprintln!(
" {} get_tarball_url(): {}",
style("•").dim(),
url
),
Err(e) => miseprintln!(
" {} get_tarball_url() error: {}",
style("•").dim(),
e
),
}
match backend.get_github_release_info(dummy_tool_version.as_ref(), &target) {
Ok(info) => miseprintln!(
" {} get_github_release_info(): {:?}",
style("•").dim(),
info
),
Err(e) => miseprintln!(
" {} get_github_release_info() error: {}",
style("•").dim(),
e
),
}
match backend.resolve_lock_info(dummy_tool_version.as_ref(), &target) {
Ok(info) => miseprintln!(
" {} resolve_lock_info(): {:?}",
style("•").dim(),
info
),
Err(e) => miseprintln!(
" {} resolve_lock_info() error: {}",
style("•").dim(),
e
),
}

Copilot uses AI. Check for mistakes.
}
}
}
}

Ok(())
}
}

// Note: We'll need to make Lockfile::read public in src/lockfile.rs
Expand Down
Loading