diff --git a/.agents/skills/code-test/SKILL.md b/.agents/skills/code-test/SKILL.md index 55d28d2a1..7d380a57d 100644 --- a/.agents/skills/code-test/SKILL.md +++ b/.agents/skills/code-test/SKILL.md @@ -62,7 +62,7 @@ Examples: ```bash just test-unit [EXTRA_FLAGS] ``` -Runs only unit tests, excluding integration tests and ampup package. Uses `cargo nextest run --workspace --exclude tests --exclude ampup`. +Runs only unit tests, excluding integration tests. Uses `cargo nextest run --workspace --exclude tests`. **⚠️ WARNING**: Some unit tests may require external dependencies (e.g., PostgreSQL for metadata-db tests). @@ -86,17 +86,6 @@ Examples: - `just test-it` - run all integration tests - `just test-it test_name` - run specific integration test -### Run Ampup Tests (REQUIRES EXTERNAL DEPENDENCIES) -```bash -just test-ampup [EXTRA_FLAGS] -``` -Runs tests for the ampup package. Uses `cargo nextest run --package ampup`. - -**⚠️ WARNING**: May require external dependencies. - -Examples: -- `just test-ampup` - run ampup tests - ## Important Guidelines ### Cargo Nextest vs Cargo Test @@ -120,7 +109,7 @@ This test command is pre-approved and can be run without user permission: 1. **During local development**: Prefer targeted tests first; use `just test-local` only for broader confidence 2. **Before commits (local)**: Run the smallest relevant test scope; broaden only if the change is risky or cross-cutting 3. **In CI environments**: The CI system will run `just test` or other commands -4. **Local development**: Never run `just test`, `just test-unit`, `just test-it`, or `just test-ampup` locally. Those are for CI +4. **Local development**: Never run `just test`, `just test-unit`, or `just test-it` locally. Those are for CI 5. **Codex sandbox**: Run tests only when warranted; prefer targeted scope. If running `just test-local`, request escalation (outside the sandbox) ### External Dependencies Required by Non-Local Tests diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53ecec9e7..7d95abced 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,12 +122,12 @@ jobs: - name: Build binaries run: | export RUSTFLAGS="-C link-arg=-fuse-ld=mold ${RUSTFLAGS}" - cargo build --target ${{ matrix.target }} --release -v -p ampd -p ampctl -p ampup -p ampsync + cargo build --target ${{ matrix.target }} --release -v -p ampd -p ampctl -p ampsync - name: Compress debug sections (Linux only) if: contains(matrix.target, 'linux') run: | - for binary in ampd ampctl ampup ampsync; do + for binary in ampd ampctl ampsync; do path="target/${{ matrix.target }}/release/$binary" size_before=$(stat -c%s "$path") objcopy --compress-debug-sections=zlib-gnu "$path" @@ -142,7 +142,6 @@ jobs: path: | target/${{ matrix.target }}/release/ampd target/${{ matrix.target }}/release/ampctl - target/${{ matrix.target }}/release/ampup target/${{ matrix.target }}/release/ampsync compression-level: 0 retention-days: 30 @@ -173,7 +172,7 @@ jobs: rustflags: "" - name: Build binaries - run: cargo build --target ${{ matrix.target }} --release -v -p ampd -p ampctl -p ampup -p ampsync + run: cargo build --target ${{ matrix.target }} --release -v -p ampd -p ampctl -p ampsync - name: Upload artifacts uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 @@ -182,7 +181,6 @@ jobs: path: | target/${{ matrix.target }}/release/ampd target/${{ matrix.target }}/release/ampctl - target/${{ matrix.target }}/release/ampup target/${{ matrix.target }}/release/ampsync compression-level: 0 retention-days: 30 @@ -196,7 +194,7 @@ jobs: strategy: fail-fast: false matrix: - binary: [ampd, ampctl, ampup, ampsync] + binary: [ampd, ampctl, ampsync] target: [x86_64-apple-darwin, aarch64-apple-darwin] steps: @@ -297,12 +295,6 @@ jobs: cp artifacts/x86_64-apple-darwin-ampctl-notarized/ampctl release/ampctl-darwin-x86_64 cp artifacts/aarch64-apple-darwin-ampctl-notarized/ampctl release/ampctl-darwin-aarch64 - # Copy and rename ampup binaries - cp artifacts/x86_64-unknown-linux-gnu/ampup release/ampup-linux-x86_64 - cp artifacts/aarch64-unknown-linux-gnu/ampup release/ampup-linux-aarch64 - cp artifacts/x86_64-apple-darwin-ampup-notarized/ampup release/ampup-darwin-x86_64 - cp artifacts/aarch64-apple-darwin-ampup-notarized/ampup release/ampup-darwin-aarch64 - # Copy and rename ampsync binaries cp artifacts/x86_64-unknown-linux-gnu/ampsync release/ampsync-linux-x86_64 cp artifacts/aarch64-unknown-linux-gnu/ampsync release/ampsync-linux-aarch64 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ef85ed1f..85ee12fa2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,32 +113,6 @@ jobs: RPC_ETH_BASE_URL: ${{ secrets.RPC_ETH_BASE_URL }} SOLANA_MAINNET_HTTP_URL: ${{ secrets.SOLANA_MAINNET_HTTP_URL }} - ampup-tests: - name: Test ampup - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1 - with: - cache: true - rustflags: "" - - - name: Setup just - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 - - - name: Install cargo-nextest - uses: baptiste0928/cargo-install@b687c656bda5733207e629b50a22bf68974a0305 # v3 - with: - crate: cargo-nextest - version: ^0.9 - - - name: Run ampup tests - run: just test-ampup --verbose - env: - GITHUB_TOKEN: ${{ github.token }} - rustfmt: name: Check rustfmt style runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 443263dcc..f1b00ad14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1310,25 +1310,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "ampup" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "console", - "dialoguer", - "fs-err", - "futures", - "indicatif", - "reqwest 0.13.2", - "semver 1.0.27", - "serde", - "tempfile", - "tokio", - "vergen-gitcl", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -4482,18 +4463,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "dialoguer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96" -dependencies = [ - "console", - "shell-words", - "tempfile", - "zeroize", -] - [[package]] name = "diff" version = "0.1.13" @@ -9867,12 +9836,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-words" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" - [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index c0e21d0ab..1a3b647c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "crates/bin/ampcc", "crates/bin/ampd", "crates/bin/ampsync", - "crates/bin/ampup", "crates/clients/flight", "crates/config", "crates/config/gen", diff --git a/README.md b/README.md index 0e8d78779..3f3feaa03 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ampup build --pr 123 ampup build --branch develop ``` -For more details and advanced options, see the [ampup README](crates/bin/ampup/README.md). +For more details and advanced options, see the [ampup README](https://github.com/edgeandnode/ampup?tab=readme-ov-file#ampup). ### Installation via Nix diff --git a/crates/bin/ampup/Cargo.toml b/crates/bin/ampup/Cargo.toml deleted file mode 100644 index 8af2a9447..000000000 --- a/crates/bin/ampup/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "ampup" -version.workspace = true -license-file.workspace = true -edition.workspace = true -build = "build.rs" - -[lib] -name = "ampup" -path = "src/lib.rs" - -[[bin]] -name = "ampup" -path = "src/main.rs" - -[dependencies] -anyhow.workspace = true -clap.workspace = true -fs-err.workspace = true -futures.workspace = true -reqwest.workspace = true -semver.workspace = true -serde.workspace = true -tempfile.workspace = true -tokio.workspace = true -indicatif = "0.18" -console = "0.16" -dialoguer = "0.12" - -[build-dependencies] -vergen-gitcl = { version = "9.0.0", features = ["build"] } diff --git a/crates/bin/ampup/README.md b/crates/bin/ampup/README.md deleted file mode 100644 index 49019f0e3..000000000 --- a/crates/bin/ampup/README.md +++ /dev/null @@ -1,214 +0,0 @@ -# ampup - -The official version manager and installer for amp. - -## Installation - -### Quick Install - -```sh -# Install ampup -curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/edgeandnode/amp/refs/heads/main/install.sh | sh -``` - -Once installed, you can conveniently manage your `ampd` versions through `ampup`. - -### Customizing Installation - -The installer script accepts options to customize the installation process: - -```sh -# Skip automatic PATH modification -curl ... | sh -s -- --no-modify-path - -# Skip installing the latest ampd version -curl ... | sh -s -- --no-install-latest - -# Use a custom installation directory -curl ... | sh -s -- --install-dir /custom/path - -# Combine multiple options -curl ... | sh -s -- --no-modify-path --no-install-latest --install-dir ~/.custom/amp -``` - -**Available Options:** - -- `--install-dir `: Install to a custom directory (default: `$XDG_CONFIG_HOME/.amp` or `$HOME/.amp`) -- `--no-modify-path`: Don't automatically add `ampup` to your PATH -- `--no-install-latest`: Don't automatically install the latest `ampd` version - -## Usage - -### Install Latest Version - -> NOTE: By default, the `ampup` installer will also install the latest version of `ampd`. - -```sh -ampup -``` - -or - -```sh -ampup install -``` - -### Install Specific Version - -```sh -ampup install v0.1.0 -``` - -### List Installed Versions - -```sh -ampup list -``` - -### Switch Between Versions - -```sh -ampup use v0.1.0 -``` - -### Uninstall a Version - -```sh -ampup uninstall v0.1.0 -``` - -### Build from Source - -Build from the default repository's main branch: - -```sh -ampup build -``` - -Build from a specific branch: - -```sh -ampup build --branch main -``` - -Build from a specific commit: - -```sh -ampup build --commit abc123 -``` - -Build from a Pull Request: - -```sh -ampup build --pr 123 -``` - -Build from a local repository: - -```sh -ampup build --path /path/to/amp -``` - -Build from a custom repository: - -```sh -ampup build --repo username/fork -``` - -Combine options (e.g., custom repo + branch): - -```sh -ampup build --repo username/fork --branch develop -``` - -Build with a custom version name: - -```sh -ampup build --path . --name my-custom-build -``` - -Build with specific number of jobs: - -```sh -ampup build --branch main --jobs 8 -``` - -### Update ampup Itself - -```sh -ampup update -``` - -## How It Works - -`ampup` is a Rust-based version manager with a minimal bootstrap script for installation. - -### Installation Methods - -1. **Precompiled Binaries** (default): Downloads signed binaries from [GitHub releases](https://github.com/edgeandnode/amp/releases) -2. **Build from Source**: Clones and compiles the repository using Cargo - -### Directory Structure - -``` -~/.amp/ -├── bin/ -│ ├── ampup # Version manager binary -│ └── ampd # Symlink to active version -├── versions/ -│ ├── v0.1.0/ -│ │ └── ampd # Binary for v0.1.0 -│ └── v0.2.0/ -│ └── ampd # Binary for v0.2.0 -└── .version # Tracks active version -``` - -## Supported Platforms - -- Linux (x86_64, aarch64) -- macOS (aarch64/Apple Silicon) - -## Environment Variables - -- `GITHUB_TOKEN`: GitHub personal access token for private repository access -- `AMP_REPO`: Override repository (default: `edgeandnode/amp`) -- `AMP_DIR`: Override installation directory (default: `$XDG_CONFIG_HOME/.amp` or `$HOME/.amp`) - -## Security - -- macOS binaries are code-signed and notarized -- Private repository access uses GitHub's OAuth token mechanism - -## Troubleshooting - -### Command not found: ampup - -Make sure the `ampup` binary is in your PATH. You may need to restart your terminal or run: - -```sh -source ~/.bashrc # or ~/.zshenv for zsh, or ~/.config/fish/config.fish for fish -``` - -### Download failed - -- Check your internet connection -- Verify the release exists on GitHub -- For private repos, ensure `GITHUB_TOKEN` is set correctly - -### Building from source requires Rust - -If you're building from source (using the `build` command), you need: - -- Rust toolchain (install from https://rustup.rs) -- Git -- Build dependencies (see main project documentation) - -## Uninstalling - -To uninstall ampd and ampup, simply delete you `.amp` directory (default: `$XDG_CONFIG_HOME/.amp` or `$HOME/.amp`): - -```sh -rm -rf ~/.amp # or $XDG_CONFIG_HOME/.amp -``` - -Then remove the PATH entry from your shell configuration file. diff --git a/crates/bin/ampup/build.rs b/crates/bin/ampup/build.rs deleted file mode 100644 index 08991ae48..000000000 --- a/crates/bin/ampup/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder}; - -type BoxError = Box; - -fn main() -> Result<(), BoxError> { - let build = BuildBuilder::all_build()?; - let gitcl = GitclBuilder::default().describe(true, true, None).build()?; - Emitter::new() - .add_instructions(&build)? - .add_instructions(&gitcl)? - .emit()?; - Ok(()) -} diff --git a/crates/bin/ampup/src/builder.rs b/crates/bin/ampup/src/builder.rs deleted file mode 100644 index d7872e987..000000000 --- a/crates/bin/ampup/src/builder.rs +++ /dev/null @@ -1,623 +0,0 @@ -use std::{ - fmt, - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; - -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::{DEFAULT_REPO, ui, version_manager::VersionManager}; - -#[derive(Debug)] -pub enum BuildError { - LocalPathNotFound { - path: PathBuf, - }, - LocalPathNotDirectory { - path: PathBuf, - }, - LocalPathNotGitRepo { - path: PathBuf, - }, - GitCloneFailed { - repo: String, - branch: Option, - }, - GitCheckoutFailed { - target: String, - }, - GitFetchPrFailed { - pr: u32, - }, - CargoBuildFailed, - BinaryNotFound { - path: PathBuf, - }, - CommandNotFound { - command: String, - }, -} - -impl std::fmt::Display for BuildError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::LocalPathNotFound { path } => { - writeln!(f, "Local path does not exist")?; - writeln!(f, " Path: {}", path.display())?; - writeln!(f)?; - writeln!(f, " Check that the path is correct and accessible.")?; - } - Self::LocalPathNotDirectory { path } => { - writeln!(f, "Local path is not a directory")?; - writeln!(f, " Path: {}", path.display())?; - writeln!(f)?; - writeln!( - f, - " The build command requires a directory containing a Cargo workspace." - )?; - } - Self::LocalPathNotGitRepo { path } => { - writeln!(f, "Local path is not a git repository")?; - writeln!(f, " Path: {}", path.display())?; - writeln!(f)?; - writeln!( - f, - " Use --name flag to specify a version name for non-git builds." - )?; - writeln!( - f, - " Example: ampup build --path {} --name my-version", - path.display() - )?; - } - Self::GitCloneFailed { repo, branch } => { - writeln!(f, "Failed to clone repository")?; - writeln!(f, " Repository: {}", repo)?; - if let Some(b) = branch { - writeln!(f, " Branch: {}", b)?; - } - writeln!(f)?; - writeln!(f, " Ensure the repository exists and is accessible.")?; - writeln!(f, " Check your network connection and GitHub permissions.")?; - } - Self::GitCheckoutFailed { target } => { - writeln!(f, "Failed to checkout git reference")?; - writeln!(f, " Target: {}", target)?; - writeln!(f)?; - writeln!(f, " The commit/branch may not exist in the repository.")?; - } - Self::GitFetchPrFailed { pr } => { - writeln!(f, "Failed to fetch pull request")?; - writeln!(f, " PR: #{}", pr)?; - writeln!(f)?; - writeln!(f, " Ensure the pull request exists and is accessible.")?; - } - Self::CargoBuildFailed => { - writeln!(f, "Cargo build failed")?; - writeln!(f)?; - writeln!(f, " Check the build output above for compilation errors.")?; - writeln!( - f, - " Ensure all dependencies are installed and the code compiles." - )?; - } - Self::BinaryNotFound { path } => { - writeln!(f, "Binary not found after build")?; - writeln!(f, " Expected: {}", path.display())?; - writeln!(f)?; - writeln!(f, " Build succeeded but the ampd binary was not created.")?; - writeln!( - f, - " This may indicate an issue with the build configuration." - )?; - } - Self::CommandNotFound { command } => { - writeln!(f, "Required command not found")?; - writeln!(f, " Command: {}", command)?; - writeln!(f)?; - match command.as_str() { - "git" => { - writeln!(f, " Install git:")?; - writeln!(f, " macOS: brew install git")?; - writeln!(f, " Ubuntu/Debian: sudo apt install git")?; - } - "cargo" => { - writeln!(f, " Install Rust toolchain:")?; - writeln!( - f, - " curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" - )?; - } - _ => { - writeln!( - f, - " Please install {} and ensure it's in your PATH.", - command - )?; - } - } - } - } - Ok(()) - } -} - -impl std::error::Error for BuildError {} - -/// Represents the source from which to build ampd -pub enum BuildSource { - /// Build from a local repository path - Local { path: PathBuf }, - /// Build from a specific branch - Branch { repo: String, branch: String }, - /// Build from a specific commit - Commit { repo: String, commit: String }, - /// Build from a pull request - Pr { repo: String, number: u32 }, - /// Build from main branch - Main { repo: String }, -} - -impl BuildSource { - /// Generate version label for this build source - fn generate_version_label(&self, git_hash: Option<&str>, name: Option<&str>) -> String { - // Custom name always takes precedence - if let Some(name) = name { - return name.to_string(); - } - - // Generate base label - let base = match self { - Self::Local { .. } => "local".to_string(), - Self::Branch { repo, branch } => { - if repo != DEFAULT_REPO { - let slug = repo.replace('/', "-"); - format!("{}-branch-{}", slug, branch) - } else { - format!("branch-{}", branch) - } - } - Self::Commit { repo, commit } => { - // Commit already has hash in it, don't append git hash later - let commit_hash = &commit[..8.min(commit.len())]; - if repo != DEFAULT_REPO { - let slug = repo.replace('/', "-"); - return format!("{}-commit-{}", slug, commit_hash); - } else { - return format!("commit-{}", commit_hash); - } - } - Self::Pr { repo, number } => { - if repo != DEFAULT_REPO { - let slug = repo.replace('/', "-"); - format!("{}-pr-{}", slug, number) - } else { - format!("pr-{}", number) - } - } - Self::Main { repo } => { - if repo != DEFAULT_REPO { - let slug = repo.replace('/', "-"); - format!("{}-main", slug) - } else { - "main".to_string() - } - } - }; - - // Append git hash if available - if let Some(hash) = git_hash { - format!("{}-{}", base, hash) - } else { - base - } - } -} - -impl fmt::Display for BuildSource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Local { path } => write!(f, "local path: {}", path.display()), - Self::Branch { repo, branch } => { - if repo != DEFAULT_REPO { - write!(f, "repository: {}, branch: {}", repo, branch) - } else { - write!(f, "branch: {}", branch) - } - } - Self::Commit { repo, commit } => { - if repo != DEFAULT_REPO { - write!(f, "repository: {}, commit: {}", repo, commit) - } else { - write!(f, "commit: {}", commit) - } - } - Self::Pr { repo, number } => { - if repo != DEFAULT_REPO { - write!(f, "repository: {}, pull request #{}", repo, number) - } else { - write!(f, "pull request #{}", number) - } - } - Self::Main { repo } => { - if repo != DEFAULT_REPO { - write!(f, "repository: {} (main branch)", repo) - } else { - write!(f, "default repository (main branch)") - } - } - } - } -} - -/// Options for building ampd -pub struct BuildOptions { - /// Custom version name - pub name: Option, - /// Number of CPU cores to use - pub jobs: Option, -} - -/// Builder for ampd from source -pub struct Builder { - version_manager: VersionManager, -} - -impl Builder { - pub fn new(version_manager: VersionManager) -> Self { - Self { version_manager } - } - - /// Execute the build for a given source - pub async fn build(&self, source: BuildSource, options: BuildOptions) -> Result<()> { - match &source { - BuildSource::Local { path } => { - // Validate path exists and is a directory - if !path.exists() { - return Err(BuildError::LocalPathNotFound { path: path.clone() }.into()); - } - if !path.is_dir() { - return Err(BuildError::LocalPathNotDirectory { path: path.clone() }.into()); - } - - // Check for git repository and extract commit hash - let git = GitRepo::new(path); - let git_hash = git.get_commit_hash()?; - - // If not a git repo and no custom name provided, error out - if git_hash.is_none() && options.name.is_none() { - return Err(BuildError::LocalPathNotGitRepo { path: path.clone() }.into()); - } - - // Generate version label and build - let version_label = - source.generate_version_label(git_hash.as_deref(), options.name.as_deref()); - build_and_install(&self.version_manager, path, &version_label, options.jobs)?; - - Ok(()) - } - BuildSource::Branch { repo, branch } => { - let temp_dir = - tempfile::tempdir().context("Failed to create temporary directory")?; - - // Clone repository with specific branch - let git = GitRepo::clone(repo, temp_dir.path(), Some(branch.as_str())).await?; - - // Extract git commit hash, generate version label, and build - let git_hash = git.get_commit_hash()?; - let version_label = - source.generate_version_label(git_hash.as_deref(), options.name.as_deref()); - build_and_install( - &self.version_manager, - temp_dir.path(), - &version_label, - options.jobs, - )?; - - Ok(()) - } - BuildSource::Commit { repo, commit } => { - let temp_dir = - tempfile::tempdir().context("Failed to create temporary directory")?; - - // Clone repository and checkout specific commit - let git = GitRepo::clone(repo, temp_dir.path(), None).await?; - git.checkout_commit(commit)?; - - // Extract git commit hash, generate version label, and build - let git_hash = git.get_commit_hash()?; - let version_label = - source.generate_version_label(git_hash.as_deref(), options.name.as_deref()); - build_and_install( - &self.version_manager, - temp_dir.path(), - &version_label, - options.jobs, - )?; - - Ok(()) - } - BuildSource::Pr { repo, number } => { - let temp_dir = - tempfile::tempdir().context("Failed to create temporary directory")?; - - // Clone repository and checkout pull request - let git = GitRepo::clone(repo, temp_dir.path(), None).await?; - git.fetch_and_checkout_pr(*number)?; - - // Extract git commit hash, generate version label, and build - let git_hash = git.get_commit_hash()?; - let version_label = - source.generate_version_label(git_hash.as_deref(), options.name.as_deref()); - build_and_install( - &self.version_manager, - temp_dir.path(), - &version_label, - options.jobs, - )?; - - Ok(()) - } - BuildSource::Main { repo } => { - let temp_dir = - tempfile::tempdir().context("Failed to create temporary directory")?; - - // Clone repository (main branch) - let git = GitRepo::clone(repo, temp_dir.path(), None).await?; - - // Extract git commit hash, generate version label, and build - let git_hash = git.get_commit_hash()?; - let version_label = - source.generate_version_label(git_hash.as_deref(), options.name.as_deref()); - build_and_install( - &self.version_manager, - temp_dir.path(), - &version_label, - options.jobs, - )?; - - Ok(()) - } - } - } -} - -/// Git repository operations -pub struct GitRepo<'a> { - path: &'a Path, - remote: String, -} - -impl<'a> GitRepo<'a> { - /// Create a new GitRepo instance for an existing repository - pub fn new(path: &'a Path) -> Self { - Self { - path, - remote: "origin".to_string(), - } - } - - /// Clone a repository from GitHub and create a GitRepo instance - pub async fn clone(repo: &str, destination: &'a Path, branch: Option<&str>) -> Result { - check_command_exists("git")?; - - let repo_url = format!("https://github.com/{}.git", repo); - - ui::info!("Cloning {}", repo_url); - - let mut args = vec!["clone"]; - - if let Some(branch) = branch { - args.extend(["--branch", branch]); - } - - args.push(&repo_url); - args.push(destination.to_str().unwrap()); - - let status = Command::new("git") - .args(&args) - .status() - .context("Failed to execute git clone")?; - - if !status.success() { - return Err(BuildError::GitCloneFailed { - repo: repo.to_string(), - branch: branch.map(|s| s.to_string()), - } - .into()); - } - - Ok(Self::new(destination)) - } - - /// Get the commit hash from this repository - /// Returns None if the path is not a git repository - pub fn get_commit_hash(&self) -> Result> { - // Check if .git directory exists - if !self.path.join(".git").exists() { - return Ok(None); - } - - // Try to get the commit hash - let output = Command::new("git") - .args(["rev-parse", "--short=8", "HEAD"]) - .current_dir(self.path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .context("Failed to execute git rev-parse")?; - - if !output.status.success() { - return Ok(None); - } - - let hash = String::from_utf8(output.stdout) - .context("Failed to parse git output")? - .trim() - .to_string(); - - Ok(Some(hash)) - } - - /// Checkout a specific commit - pub fn checkout_commit(&self, commit: &str) -> Result<()> { - let status = Command::new("git") - .args(["checkout", commit]) - .current_dir(self.path) - .status() - .context("Failed to execute git checkout")?; - - if !status.success() { - return Err(BuildError::GitCheckoutFailed { - target: commit.to_string(), - } - .into()); - } - - Ok(()) - } - - /// Fetch and checkout a pull request - pub fn fetch_and_checkout_pr(&self, number: u32) -> Result<()> { - // Fetch the PR - let pr_ref = format!("pull/{}/head:pr-{}", number, number); - let status = Command::new("git") - .args(["fetch", &self.remote, &pr_ref]) - .current_dir(self.path) - .status() - .context("Failed to execute git fetch")?; - - if !status.success() { - return Err(BuildError::GitFetchPrFailed { pr: number }.into()); - } - - // Checkout the PR - let status = Command::new("git") - .args(["checkout", &format!("pr-{}", number)]) - .current_dir(self.path) - .status() - .context("Failed to execute git checkout")?; - - if !status.success() { - return Err(BuildError::GitCheckoutFailed { - target: format!("PR #{}", number), - } - .into()); - } - - Ok(()) - } -} - -/// Build and install the ampd and ampctl binaries -fn build_and_install( - version_manager: &VersionManager, - repo_path: &Path, - version_label: &str, - jobs: Option, -) -> Result<()> { - check_command_exists("cargo")?; - - ui::info!("Building ampd and ampctl"); - - let mut args = vec!["build", "--release", "-p", "ampd", "-p", "ampctl"]; - - let jobs_str; - if let Some(j) = jobs { - jobs_str = j.to_string(); - args.extend(["-j", &jobs_str]); - } - - let status = Command::new("cargo") - .args(&args) - .current_dir(repo_path) - .status() - .context("Failed to execute cargo build")?; - - if !status.success() { - return Err(BuildError::CargoBuildFailed.into()); - } - - // Find the built binaries - let ampd_source = repo_path.join("target/release/ampd"); - let ampctl_source = repo_path.join("target/release/ampctl"); - - if !ampd_source.exists() { - return Err(BuildError::BinaryNotFound { - path: ampd_source.clone(), - } - .into()); - } - - if !ampctl_source.exists() { - return Err(BuildError::BinaryNotFound { - path: ampctl_source.clone(), - } - .into()); - } - - let config = version_manager.config(); - - // Create version directory - let version_dir = config.versions_dir.join(version_label); - fs::create_dir_all(&version_dir).context("Failed to create version directory")?; - - // Copy ampd binary - let ampd_dest = version_dir.join("ampd"); - fs::copy(&d_source, &d_dest).context("Failed to copy ampd binary")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&d_dest) - .context("Failed to get ampd metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&d_dest, perms) - .context("Failed to set executable permissions on ampd")?; - } - - // Copy ampctl binary - let ampctl_dest = version_dir.join("ampctl"); - fs::copy(&ctl_source, &ctl_dest).context("Failed to copy ampctl binary")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&ctl_dest) - .context("Failed to get ampctl metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&ctl_dest, perms) - .context("Failed to set executable permissions on ampctl")?; - } - - // Activate this version - version_manager.activate(version_label)?; - - ui::success!( - "Built and installed ampd and ampctl {}", - ui::version(version_label) - ); - ui::detail!("Run 'ampd --version' and 'ampctl --version' to verify installation"); - - Ok(()) -} - -/// Check if a command exists -fn check_command_exists(command: &str) -> Result<()> { - let status = Command::new(command) - .arg("--version") - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .status(); - - match status { - Ok(_) => Ok(()), - Err(_) => Err(BuildError::CommandNotFound { - command: command.to_string(), - } - .into()), - } -} diff --git a/crates/bin/ampup/src/commands.rs b/crates/bin/ampup/src/commands.rs deleted file mode 100644 index a361ccfca..000000000 --- a/crates/bin/ampup/src/commands.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod build; -pub mod init; -pub mod install; -pub mod list; -pub mod uninstall; -pub mod update; -pub mod use_version; diff --git a/crates/bin/ampup/src/commands/build.rs b/crates/bin/ampup/src/commands/build.rs deleted file mode 100644 index ea445afef..000000000 --- a/crates/bin/ampup/src/commands/build.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::path::PathBuf; - -use anyhow::Result; - -use crate::{ - DEFAULT_REPO_PRIVATE, - builder::{BuildOptions, BuildSource, Builder}, - config::Config, - ui, - version_manager::VersionManager, -}; - -/// Main entry point for build command - handles all build source combinations -#[expect(clippy::too_many_arguments)] -pub async fn run( - install_dir: Option, - repo: Option, - path: Option, - branch: Option, - commit: Option, - pr: Option, - name: Option, - jobs: Option, -) -> Result<()> { - // Determine build source based on provided options - let source = match (path, repo, branch, commit, pr) { - (Some(path), _, None, None, None) => BuildSource::Local { path }, - (None, Some(repo), Some(branch), None, None) => BuildSource::Branch { repo, branch }, - (None, Some(repo), None, Some(commit), None) => BuildSource::Commit { repo, commit }, - (None, Some(repo), None, None, Some(number)) => BuildSource::Pr { repo, number }, - (None, Some(repo), None, None, None) => BuildSource::Main { repo }, - (None, None, Some(branch), None, None) => BuildSource::Branch { - repo: DEFAULT_REPO_PRIVATE.to_string(), - branch, - }, - (None, None, None, Some(commit), None) => BuildSource::Commit { - repo: DEFAULT_REPO_PRIVATE.to_string(), - commit, - }, - (None, None, None, None, Some(number)) => BuildSource::Pr { - repo: DEFAULT_REPO_PRIVATE.to_string(), - number, - }, - (None, None, None, None, None) => BuildSource::Main { - repo: DEFAULT_REPO_PRIVATE.to_string(), - }, - _ => unreachable!("Clap should prevent conflicting options"), - }; - - ui::info!("Building from source: {}", source); - - // Create builder - let config = Config::new(install_dir)?; - let version_manager = VersionManager::new(config); - let builder = Builder::new(version_manager); - - // Execute the build - builder.build(source, BuildOptions { name, jobs }).await?; - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/init.rs b/crates/bin/ampup/src/commands/init.rs deleted file mode 100644 index 20ce0481b..000000000 --- a/crates/bin/ampup/src/commands/init.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::{DEFAULT_REPO, config::Config, shell, ui}; - -#[derive(Debug)] -pub enum InitError { - AlreadyInitialized { install_dir: PathBuf }, -} - -impl std::fmt::Display for InitError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::AlreadyInitialized { install_dir } => { - writeln!(f, "ampup is already initialized")?; - writeln!(f, " Installation: {}", install_dir.display())?; - writeln!(f)?; - writeln!(f, " If you want to reinstall, remove the directory first:")?; - writeln!(f, " rm -rf {}", install_dir.display())?; - } - } - Ok(()) - } -} - -impl std::error::Error for InitError {} - -pub async fn run( - install_dir: Option, - no_modify_path: bool, - no_install_latest: bool, - github_token: Option, -) -> Result<()> { - // Create config to get all the paths - let config = Config::new(install_dir)?; - let ampup_path = config.ampup_binary_path(); - - // Check if already initialized - if ampup_path.exists() { - return Err(InitError::AlreadyInitialized { - install_dir: config.amp_dir.clone(), - } - .into()); - } - - ui::info!("Installing to {}", ui::path(config.amp_dir.display())); - - // Create directory structure using Config's ensure_dirs - config.ensure_dirs()?; - - // Copy self to installation directory - let current_exe = std::env::current_exe().context("Failed to get current executable path")?; - fs::copy(¤t_exe, &up_path).with_context(|| { - format!( - "Failed to copy ampup from {} to {}", - current_exe.display(), - ampup_path.display() - ) - })?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&up_path) - .context("Failed to get ampup binary metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&up_path, perms) - .context("Failed to set ampup binary permissions")?; - } - - ui::success!("Installed ampup to {}", ui::path(ampup_path.display())); - - // Modify PATH if requested - if !no_modify_path { - let bin_dir_str = config.bin_dir.to_string_lossy(); - if let Err(e) = shell::add_to_path(&bin_dir_str) { - ui::warn!("Failed to add to PATH: {}", e); - ui::detail!("Please manually add {} to your PATH", bin_dir_str); - } - } else { - ui::detail!( - "Skipping PATH modification. Add {} to your PATH manually", - ui::path(config.bin_dir.display()) - ); - } - - // Install latest ampd if requested - if !no_install_latest { - ui::info!("Installing latest ampd version"); - // We'll use the existing install command - crate::commands::install::run( - Some(config.amp_dir), - DEFAULT_REPO.to_string(), - github_token, - None, - None, - None, - ) - .await?; - } else { - ui::detail!("Skipping installation of latest ampd"); - ui::detail!("Run 'ampup install' to install ampd when ready"); - } - - ui::success!("Installation complete!"); - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/install.rs b/crates/bin/ampup/src/commands/install.rs deleted file mode 100644 index 684c4bdfd..000000000 --- a/crates/bin/ampup/src/commands/install.rs +++ /dev/null @@ -1,94 +0,0 @@ -use anyhow::Result; - -use crate::{ - config::Config, - github::GitHubClient, - install::Installer, - platform::{Architecture, Platform}, - ui, - version_manager::VersionManager, -}; - -pub async fn run( - install_dir: Option, - repo: String, - github_token: Option, - version: Option, - arch_override: Option, - platform_override: Option, -) -> Result<()> { - let config = Config::new(install_dir)?; - let github = GitHubClient::new(repo, github_token)?; - let version_manager = VersionManager::new(config); - - // Determine version to install - let version = match version { - Some(v) => v, - None => { - ui::info!("Fetching latest version"); - github.get_latest_version().await? - } - }; - - // Check if this version is already installed - if version_manager.is_installed(&version) { - ui::info!("Version {} is already installed", ui::version(&version)); - - // Check if it's the current version - let current_version = version_manager.get_current()?; - if current_version.as_deref() == Some(&version) { - ui::success!("Already using version {}", ui::version(&version)); - return Ok(()); - } - - // Switch to this version - ui::info!("Switching to version {}", ui::version(&version)); - crate::commands::use_version::switch_to_version(&version_manager, &version)?; - ui::success!("Switched to version {}", ui::version(&version)); - ui::detail!("Run 'ampd --version' and 'ampctl --version' to verify installation"); - return Ok(()); - } - - ui::info!("Installing version {}", ui::version(&version)); - - // Detect or override platform and architecture - let platform = match platform_override { - Some(p) => match p.as_str() { - "linux" => Platform::Linux, - "darwin" => Platform::Darwin, - _ => { - return Err( - crate::platform::PlatformError::UnsupportedPlatform { detected: p }.into(), - ); - } - }, - None => Platform::detect()?, - }; - - let arch = match arch_override { - Some(a) => match a.as_str() { - "x86_64" | "amd64" => Architecture::X86_64, - "aarch64" | "arm64" => Architecture::Aarch64, - _ => { - return Err(crate::platform::PlatformError::UnsupportedArchitecture { - detected: a, - } - .into()); - } - }, - None => Architecture::detect()?, - }; - - ui::detail!("Platform: {}, Architecture: {}", platform, arch); - - // Install the binary - let installer = Installer::new(version_manager, github); - installer - .install_from_release(&version, platform, arch) - .await?; - - ui::success!("Installed ampd and ampctl {}", ui::version(&version)); - ui::detail!("Run 'ampd --version' and 'ampctl --version' to verify installation"); - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/list.rs b/crates/bin/ampup/src/commands/list.rs deleted file mode 100644 index db2dc015f..000000000 --- a/crates/bin/ampup/src/commands/list.rs +++ /dev/null @@ -1,35 +0,0 @@ -use anyhow::Result; -use console::style; - -use crate::{config::Config, ui, version_manager::VersionManager}; - -pub fn run(install_dir: Option) -> Result<()> { - let config = Config::new(install_dir)?; - let version_manager = VersionManager::new(config); - - let versions = version_manager.list_installed()?; - - if versions.is_empty() { - ui::info!("No versions installed"); - return Ok(()); - } - - let current_version = version_manager.get_current()?; - - ui::info!("Installed versions:"); - - for version in versions { - if Some(&version) == current_version.as_ref() { - println!( - " {} {} {}", - style("*").green().bold(), - style(&version).bold(), - style("(current)").dim() - ); - } else { - println!(" {}", version); - } - } - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/uninstall.rs b/crates/bin/ampup/src/commands/uninstall.rs deleted file mode 100644 index fc347bbdc..000000000 --- a/crates/bin/ampup/src/commands/uninstall.rs +++ /dev/null @@ -1,23 +0,0 @@ -use anyhow::Result; - -use crate::{config::Config, ui, version_manager::VersionManager}; - -pub fn run(install_dir: Option, version: &str) -> Result<()> { - let config = Config::new(install_dir)?; - let version_manager = VersionManager::new(config); - - // Check if this is the current version before uninstalling - let was_current = version_manager.get_current()?.as_deref() == Some(version); - - // Uninstall the version - version_manager.uninstall(version)?; - - ui::success!("Uninstalled ampd {}", ui::version(version)); - - if was_current { - ui::warn!("No version is currently active"); - ui::detail!("Run 'ampup use ' to activate a version"); - } - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/update.rs b/crates/bin/ampup/src/commands/update.rs deleted file mode 100644 index 0af3c7483..000000000 --- a/crates/bin/ampup/src/commands/update.rs +++ /dev/null @@ -1,49 +0,0 @@ -use anyhow::{Context, Result}; -use semver::Version; - -use crate::{github::GitHubClient, ui, updater::Updater}; - -pub async fn run(repo: String, github_token: Option) -> Result<()> { - ui::info!("Checking for updates"); - - let github = GitHubClient::new(repo, github_token)?; - let updater = Updater::new(github); - - let current_version = updater.get_current_version(); - let latest_version = updater.get_latest_version().await?; - - ui::info!( - "Current version: {}, Latest version: {}", - ui::version(¤t_version), - ui::version(&latest_version) - ); - - // Normalize versions for comparison (e.g., "v0.1.0-123-gabcd1234" -> "0.1.0") - let current_normalized = current_version - .split('-') - .next() - .unwrap_or(¤t_version) - .strip_prefix('v') - .unwrap_or(¤t_version); - let latest_normalized = latest_version - .split('-') - .next() - .unwrap_or(&latest_version) - .strip_prefix('v') - .unwrap_or(&latest_version); - - // Parse versions for proper semver comparison - let current_semver = - Version::parse(current_normalized).context("Failed to parse current version")?; - let latest_semver = - Version::parse(latest_normalized).context("Failed to parse latest version")?; - - if latest_semver > current_semver { - ui::info!("Updating to {}", ui::version(&latest_version)); - updater.update_self(&latest_version).await?; - } else { - ui::success!("No updates available"); - } - - Ok(()) -} diff --git a/crates/bin/ampup/src/commands/use_version.rs b/crates/bin/ampup/src/commands/use_version.rs deleted file mode 100644 index 3feb90172..000000000 --- a/crates/bin/ampup/src/commands/use_version.rs +++ /dev/null @@ -1,69 +0,0 @@ -use anyhow::{Context, Result}; -use dialoguer::{Select, theme::ColorfulTheme}; - -use crate::{ - config::Config, - ui, - version_manager::{VersionError, VersionManager}, -}; - -pub fn run(install_dir: Option, version: Option) -> Result<()> { - let config = Config::new(install_dir)?; - let version_manager = VersionManager::new(config); - - // If version is provided, use it directly, otherwise prompt user to select from installed versions - let version = match version { - Some(v) => v, - None => select_version(&version_manager)?, - }; - - switch_to_version(&version_manager, &version)?; - ui::success!("Switched to ampd {}", ui::version(&version)); - - Ok(()) -} - -/// Switch to a specific installed version -pub fn switch_to_version(version_manager: &VersionManager, version: &str) -> Result<()> { - version_manager.activate(version)?; - Ok(()) -} - -fn select_version(version_manager: &VersionManager) -> Result { - let versions = version_manager.list_installed()?; - - if versions.is_empty() { - return Err(VersionError::NoVersionsInstalled.into()); - } - - // Get current version - let current_version = version_manager.get_current()?; - - // Create display items with current indicator - let display_items: Vec = versions - .iter() - .map(|v| { - if Some(v) == current_version.as_ref() { - format!("{} (current)", v) - } else { - v.clone() - } - }) - .collect(); - - // Find default selection (current version if exists) - let default_index = current_version - .as_ref() - .and_then(|cv| versions.iter().position(|v| v == cv)) - .unwrap_or(0); - - // Show interactive selection - let selection = Select::with_theme(&ColorfulTheme::default()) - .with_prompt("Select a version to use") - .default(default_index) - .items(&display_items) - .interact() - .context("Failed to get user selection")?; - - Ok(versions[selection].clone()) -} diff --git a/crates/bin/ampup/src/config.rs b/crates/bin/ampup/src/config.rs deleted file mode 100644 index b2cb06e2d..000000000 --- a/crates/bin/ampup/src/config.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{Context, Result}; -use fs_err as fs; - -/// Configuration for ampup -pub struct Config { - /// Base directory for amp installation (~/.amp) - pub amp_dir: PathBuf, - /// Binary directory (~/.amp/bin) - pub bin_dir: PathBuf, - /// Versions directory (~/.amp/versions) - pub versions_dir: PathBuf, -} - -impl Config { - /// Create a new configuration - pub fn new(install_dir: Option) -> Result { - let amp_dir = if let Some(dir) = install_dir { - dir - } else { - let home = std::env::var("HOME") - .or_else(|_| std::env::var("USERPROFILE")) - .context("Could not determine home directory")?; - - let base = std::env::var("XDG_CONFIG_HOME") - .map(PathBuf::from) - .unwrap_or_else(|_| PathBuf::from(&home)); - - base.join(".amp") - }; - - let bin_dir = amp_dir.join("bin"); - let versions_dir = amp_dir.join("versions"); - - Ok(Self { - amp_dir, - bin_dir, - versions_dir, - }) - } - - /// Get the path to the current version file - pub fn current_version_file(&self) -> PathBuf { - self.amp_dir.join(".version") - } - - /// Get the currently installed version - pub fn current_version(&self) -> Result> { - let version_file = self.current_version_file(); - if !version_file.exists() { - return Ok(None); - } - - let version = fs::read_to_string(&version_file) - .context("Failed to read current version file")? - .trim() - .to_string(); - - Ok(Some(version)) - } - - /// Set the current version - pub fn set_current_version(&self, version: &str) -> Result<()> { - fs::create_dir_all(&self.amp_dir).context("Failed to create amp directory")?; - fs::write(self.current_version_file(), version) - .context("Failed to write current version file")?; - Ok(()) - } - - /// Get the path to the ampup binary - pub fn ampup_binary_path(&self) -> PathBuf { - self.bin_dir.join("ampup") - } - - /// Get the binary path for a specific version - pub fn version_binary_path(&self, version: &str) -> PathBuf { - self.versions_dir.join(version).join("ampd") - } - - /// Get the active ampd binary symlink path - pub fn active_binary_path(&self) -> PathBuf { - self.bin_dir.join("ampd") - } - - /// Get the ampctl binary path for a specific version - pub fn version_ampctl_path(&self, version: &str) -> PathBuf { - self.versions_dir.join(version).join("ampctl") - } - - /// Get the active ampctl binary symlink path - pub fn active_ampctl_path(&self) -> PathBuf { - self.bin_dir.join("ampctl") - } - - /// Ensure all required directories exist - pub fn ensure_dirs(&self) -> Result<()> { - fs::create_dir_all(&self.amp_dir).context("Failed to create amp directory")?; - fs::create_dir_all(&self.bin_dir).context("Failed to create bin directory")?; - fs::create_dir_all(&self.versions_dir).context("Failed to create versions directory")?; - Ok(()) - } -} - -impl Default for Config { - fn default() -> Self { - Self::new(None).expect("Failed to create default config") - } -} diff --git a/crates/bin/ampup/src/github.rs b/crates/bin/ampup/src/github.rs deleted file mode 100644 index 0192d98f1..000000000 --- a/crates/bin/ampup/src/github.rs +++ /dev/null @@ -1,397 +0,0 @@ -use anyhow::{Context, Result}; -use futures::StreamExt; -use indicatif::{ProgressBar, ProgressStyle}; -use serde::Deserialize; - -const AMPUP_API_URL: &str = "https://ampup.sh/api"; -const GITHUB_API_URL: &str = "https://api.github.com"; - -#[derive(Debug)] -pub enum GitHubError { - ReleaseNotFound { - repo: String, - has_token: bool, - url: String, - is_latest: bool, - }, - AuthFailed { - status_code: u16, - repo: String, - url: String, - }, - AssetNotFound { - repo: String, - asset_name: String, - version: String, - available_assets: Vec, - }, - DownloadFailed { - repo: String, - asset_name: String, - status_code: u16, - url: String, - }, - HttpError { - repo: String, - status_code: u16, - url: String, - body: String, - }, -} - -impl std::fmt::Display for GitHubError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ReleaseNotFound { - repo, - has_token, - url, - is_latest, - } => { - if *is_latest { - writeln!(f, "Failed to fetch latest release")?; - } else { - writeln!(f, "Failed to fetch release")?; - } - writeln!(f, " Repository: {}", repo)?; - writeln!(f, " URL: {}", url)?; - writeln!(f, " Status: 404 Not Found")?; - writeln!(f)?; - if *has_token { - writeln!( - f, - " The repository may not exist, or no releases have been published yet." - )?; - if !*is_latest { - writeln!(f, " The specified version/tag may not exist.")?; - } - } else { - writeln!(f, " The repository is private or requires authentication.")?; - writeln!(f, " Try: export GITHUB_TOKEN=$(gh auth token)")?; - } - } - Self::AuthFailed { - status_code, - repo, - url, - } => { - writeln!(f, "Authentication failed")?; - writeln!(f, " Repository: {}", repo)?; - writeln!(f, " URL: {}", url)?; - writeln!(f, " Status: HTTP {}", status_code)?; - writeln!(f)?; - writeln!(f, " Your GITHUB_TOKEN may be invalid or expired.")?; - if *status_code == 403 { - writeln!( - f, - " For private repositories, ensure your token has 'repo' scope." - )?; - } - writeln!(f, " Try: export GITHUB_TOKEN=$(gh auth token)")?; - } - Self::AssetNotFound { - repo, - asset_name, - version, - available_assets, - } => { - writeln!(f, "Release asset not found")?; - writeln!(f, " Repository: {}", repo)?; - writeln!(f, " Asset: {}", asset_name)?; - writeln!(f, " Version: {}", version)?; - writeln!(f)?; - if available_assets.is_empty() { - writeln!(f, " No assets available in this release.")?; - } else { - writeln!(f, " Available assets:")?; - for asset in available_assets { - writeln!(f, " - {}", asset)?; - } - } - } - Self::DownloadFailed { - repo, - asset_name, - status_code, - url, - } => { - writeln!(f, "Failed to download release asset")?; - writeln!(f, " Repository: {}", repo)?; - writeln!(f, " Asset: {}", asset_name)?; - writeln!(f, " URL: {}", url)?; - writeln!(f, " Status: HTTP {}", status_code)?; - writeln!(f)?; - if *status_code == 401 || *status_code == 403 { - writeln!(f, " Authentication or permission issue.")?; - writeln!(f, " Try: export GITHUB_TOKEN=$(gh auth token)")?; - } else if *status_code == 404 { - writeln!(f, " The asset may have been removed or is not accessible.")?; - } else { - writeln!(f, " Network or server error. Please try again.")?; - } - } - Self::HttpError { - repo, - status_code, - url, - body, - } => { - writeln!(f, "Request failed")?; - writeln!(f, " Repository: {}", repo)?; - writeln!(f, " URL: {}", url)?; - writeln!(f, " Status: HTTP {}", status_code)?; - if !body.is_empty() { - writeln!(f, " Response: {}", body)?; - } - } - } - Ok(()) - } -} - -impl std::error::Error for GitHubError {} - -#[derive(Debug, Deserialize)] -struct Release { - #[serde(rename = "tag_name")] - tag: String, - assets: Vec, -} - -#[derive(Debug, Deserialize)] -struct Asset { - id: u64, - name: String, - #[serde(rename = "browser_download_url")] - url: String, -} - -pub struct GitHubClient { - client: reqwest::Client, - repo: String, - token: Option, - /// Base URL for API requests (either custom API or GitHub API) - api: String, -} - -impl GitHubClient { - pub fn new(repo: String, github_token: Option) -> Result { - let mut headers = reqwest::header::HeaderMap::new(); - headers.insert( - reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static("ampup"), - ); - - if let Some(token) = &github_token { - let auth_value = format!("Bearer {}", token); - headers.insert( - reqwest::header::AUTHORIZATION, - reqwest::header::HeaderValue::from_str(&auth_value) - .context("Invalid access token")?, - ); - } - - let client = reqwest::Client::builder() - .default_headers(headers) - .build() - .context("Failed to create request client")?; - - // Use custom endpoints for edgeandnode/amp and otherwise leverages the github api - let api = if repo == "edgeandnode/amp" && github_token.is_none() { - AMPUP_API_URL.to_string() - } else { - format!("{}/repos/{}/releases", GITHUB_API_URL, repo) - }; - - Ok(Self { - client, - repo, - token: github_token, - api, - }) - } - - /// Get the latest release version - pub async fn get_latest_version(&self) -> Result { - let release = self.get_latest_release().await?; - Ok(release.tag) - } - - /// Get the latest release - async fn get_latest_release(&self) -> Result { - self.get_release("latest").await - } - - /// Get a tagged release - async fn get_tagged_release(&self, version: &str) -> Result { - self.get_release(&format!("tags/{}", version)).await - } - - /// Fetch release from GitHub API - async fn get_release(&self, path: &str) -> Result { - let url = format!("{}/{}", self.api, path); - - let response = self - .client - .get(&url) - .send() - .await - .context("Failed to fetch release")?; - - if !response.status().is_success() { - let status = response.status(); - match status { - reqwest::StatusCode::NOT_FOUND => { - return Err(GitHubError::ReleaseNotFound { - repo: self.repo.clone(), - has_token: self.token.is_some(), - url: url.clone(), - is_latest: path == "latest", - } - .into()); - } - reqwest::StatusCode::UNAUTHORIZED | reqwest::StatusCode::FORBIDDEN => { - return Err(GitHubError::AuthFailed { - status_code: status.as_u16(), - repo: self.repo.clone(), - url: url.clone(), - } - .into()); - } - _ => { - let body = response.text().await.unwrap_or_default(); - return Err(GitHubError::HttpError { - repo: self.repo.clone(), - status_code: status.as_u16(), - url: url.clone(), - body, - } - .into()); - } - } - } - - let release: Release = response - .json() - .await - .context("Failed to parse release response")?; - - Ok(release) - } - - /// Download a release asset by name - pub async fn download_release_asset(&self, version: &str, asset_name: &str) -> Result> { - let release = self.get_tagged_release(version).await?; - - // Find the asset - let asset = release - .assets - .iter() - .find(|a| a.name == asset_name) - .ok_or_else(|| GitHubError::AssetNotFound { - repo: self.repo.clone(), - asset_name: asset_name.to_string(), - version: version.to_string(), - available_assets: release.assets.iter().map(|a| a.name.clone()).collect(), - })?; - - if self.token.is_some() { - // For private repositories, we need to use the API to download - self.download_asset_via_api(asset.id, asset_name).await - } else { - // For public repositories, use direct download URL - self.download_asset_direct(&asset.url, asset_name).await - } - } - - /// Download asset via GitHub API (for private repos) - async fn download_asset_via_api(&self, asset_id: u64, asset_name: &str) -> Result> { - let url = format!( - "https://api.github.com/repos/{}/releases/assets/{}", - self.repo, asset_id - ); - - let response = self - .client - .get(&url) - .header(reqwest::header::ACCEPT, "application/octet-stream") - .send() - .await - .context("Failed to download asset")?; - - self.download_with_progress(response, &url, asset_name) - .await - } - - /// Download asset directly (for public repos) - async fn download_asset_direct(&self, url: &str, asset_name: &str) -> Result> { - let response = self - .client - .get(url) - .send() - .await - .context("Failed to download asset")?; - - self.download_with_progress(response, url, asset_name).await - } - - /// Download with progress bar from a response - async fn download_with_progress( - &self, - response: reqwest::Response, - url: &str, - asset_name: &str, - ) -> Result> { - if !response.status().is_success() { - let status = response.status(); - return Err(GitHubError::DownloadFailed { - repo: self.repo.clone(), - asset_name: asset_name.to_string(), - status_code: status.as_u16(), - url: url.to_string(), - } - .into()); - } - - // Get content length for progress bar - let total_size = response.content_length(); - - // Setup progress bar - let pb = if let Some(size) = total_size { - let pb = ProgressBar::new(size); - pb.set_style( - ProgressStyle::default_bar() - .template( - "{msg} [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})", - ) - .context("Invalid progress bar template")? - .progress_chars("#>-"), - ); - pb.set_message(format!("{} Downloading", console::style("→").cyan())); - pb - } else { - let pb = ProgressBar::new_spinner(); - pb.set_message(format!( - "{} Downloading (size unknown)", - console::style("→").cyan() - )); - pb - }; - - // Stream and collect chunks - let mut downloaded: u64 = 0; - let mut buffer = Vec::new(); - let mut stream = response.bytes_stream(); - - while let Some(chunk) = stream.next().await { - let chunk = chunk.context("Error while downloading file")?; - buffer.extend_from_slice(&chunk); - downloaded += chunk.len() as u64; - pb.set_position(downloaded); - } - - pb.finish_with_message(format!("{} Downloaded", console::style("✓").green().bold())); - - Ok(buffer) - } -} diff --git a/crates/bin/ampup/src/install.rs b/crates/bin/ampup/src/install.rs deleted file mode 100644 index 1af9bd0d3..000000000 --- a/crates/bin/ampup/src/install.rs +++ /dev/null @@ -1,154 +0,0 @@ -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::{ - github::GitHubClient, - platform::{Architecture, Platform}, - ui, - version_manager::VersionManager, -}; - -#[derive(Debug)] -pub enum InstallError { - EmptyBinary { version: String }, -} - -impl std::fmt::Display for InstallError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::EmptyBinary { version } => { - writeln!(f, "Downloaded binary is empty")?; - writeln!(f, " Version: {}", version)?; - writeln!(f)?; - writeln!( - f, - " The release asset was downloaded but contains no data." - )?; - writeln!( - f, - " This may indicate a problem with the release packaging." - )?; - writeln!( - f, - " Try downloading a different version or report this issue." - )?; - } - } - Ok(()) - } -} - -impl std::error::Error for InstallError {} - -pub struct Installer { - version_manager: VersionManager, - github: GitHubClient, -} - -impl Installer { - pub fn new(version_manager: VersionManager, github: GitHubClient) -> Self { - Self { - version_manager, - github, - } - } - - /// Install ampd and ampctl from a GitHub release - pub async fn install_from_release( - &self, - version: &str, - platform: Platform, - arch: Architecture, - ) -> Result<()> { - self.version_manager.config().ensure_dirs()?; - - // Download and install ampd - let ampd_artifact = format!("ampd-{}-{}", platform.as_str(), arch.as_str()); - ui::info!("Downloading {} for {}", ui::version(version), ampd_artifact); - - let ampd_data = self - .github - .download_release_asset(version, &d_artifact) - .await?; - - if ampd_data.is_empty() { - return Err(InstallError::EmptyBinary { - version: version.to_string(), - } - .into()); - } - - ui::detail!("Downloaded {} bytes for ampd", ampd_data.len()); - - // Download and install ampctl - let ampctl_artifact = format!("ampctl-{}-{}", platform.as_str(), arch.as_str()); - ui::info!( - "Downloading {} for {}", - ui::version(version), - ampctl_artifact - ); - - let ampctl_data = self - .github - .download_release_asset(version, &ctl_artifact) - .await?; - - if ampctl_data.is_empty() { - return Err(InstallError::EmptyBinary { - version: version.to_string(), - } - .into()); - } - - ui::detail!("Downloaded {} bytes for ampctl", ampctl_data.len()); - - // Install both binaries - self.install_binaries(version, &d_data, &ctl_data)?; - - Ok(()) - } - - /// Install both ampd and ampctl binaries to the version directory - fn install_binaries(&self, version: &str, ampd_data: &[u8], ampctl_data: &[u8]) -> Result<()> { - let config = self.version_manager.config(); - - // Create version directory - let version_dir = config.versions_dir.join(version); - fs::create_dir_all(&version_dir).context("Failed to create version directory")?; - - // Install ampd - let ampd_path = version_dir.join("ampd"); - fs::write(&d_path, ampd_data).context("Failed to write ampd binary")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&d_path) - .context("Failed to get ampd metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&d_path, perms) - .context("Failed to set executable permissions on ampd")?; - } - - // Install ampctl - let ampctl_path = version_dir.join("ampctl"); - fs::write(&ctl_path, ampctl_data).context("Failed to write ampctl binary")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&ctl_path) - .context("Failed to get ampctl metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&ctl_path, perms) - .context("Failed to set executable permissions on ampctl")?; - } - - // Activate this version using the version manager - self.version_manager.activate(version)?; - - Ok(()) - } -} diff --git a/crates/bin/ampup/src/lib.rs b/crates/bin/ampup/src/lib.rs deleted file mode 100644 index cea1ee0cb..000000000 --- a/crates/bin/ampup/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod builder; -pub mod commands; -pub mod config; -pub mod github; -pub mod install; -pub mod platform; -pub mod shell; -pub mod updater; -pub mod version_manager; - -#[macro_use] -pub mod ui; - -/// Default GitHub repository for amp releases -pub const DEFAULT_REPO: &str = "edgeandnode/amp"; - -/// Default GitHub repository for amp development -pub const DEFAULT_REPO_PRIVATE: &str = "edgeandnode/amp-private"; - -#[cfg(test)] -mod tests; diff --git a/crates/bin/ampup/src/main.rs b/crates/bin/ampup/src/main.rs deleted file mode 100644 index feceb6f8d..000000000 --- a/crates/bin/ampup/src/main.rs +++ /dev/null @@ -1,265 +0,0 @@ -use ampup::{DEFAULT_REPO, commands}; -use console::style; - -/// The ampd installer and version manager -#[derive(Debug, clap::Parser)] -#[command(name = "ampup")] -#[command(about = "The ampd installer and version manager", long_about = None)] -#[command(version = env!("VERGEN_GIT_DESCRIBE"))] -struct Cli { - #[command(subcommand)] - command: Option, -} - -#[derive(Debug, clap::Subcommand)] -enum Commands { - /// Initialize ampup (called by install script) - #[command(hide = true)] - Init { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// Don't modify PATH environment variable - #[arg(long)] - no_modify_path: bool, - - /// Don't install latest ampd version after setup - #[arg(long)] - no_install_latest: bool, - - /// GitHub token for private repository access (defaults to $GITHUB_TOKEN) - #[arg(long, env = "GITHUB_TOKEN", hide_env = true)] - github_token: Option, - }, - - /// Install a specific version from binaries (default: latest) - Install { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// Version to install (e.g., v0.1.0). If not specified, installs latest - version: Option, - - /// GitHub repository in format "owner/repo" - #[arg(long, default_value_t = DEFAULT_REPO.to_string())] - repo: String, - - /// GitHub token for private repository access (defaults to $GITHUB_TOKEN) - #[arg(long, env = "GITHUB_TOKEN", hide_env = true)] - github_token: Option, - - /// Override architecture detection (x86_64, aarch64) - #[arg(long)] - arch: Option, - - /// Override platform detection (linux, darwin) - #[arg(long)] - platform: Option, - }, - - /// List installed versions - List { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - }, - - /// Switch to a specific installed version - Use { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// Version to switch to (if not provided, shows interactive selection) - version: Option, - }, - - /// Uninstall a specific version - Uninstall { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// Version to uninstall - version: String, - }, - - /// Build and install from source - Build { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// Build from local repository path - #[arg(short, long, conflicts_with_all = ["repo", "branch", "commit", "pr"])] - path: Option, - - /// GitHub repository in format "owner/repo" - #[arg(short, long, conflicts_with = "path")] - repo: Option, - - /// Build from specific branch - #[arg(short, long, conflicts_with_all = ["path", "commit", "pr"])] - branch: Option, - - /// Build from specific commit hash - #[arg(short = 'C', long, conflicts_with_all = ["path", "branch", "pr"])] - commit: Option, - - /// Build from pull request number - #[arg(short = 'P', long, conflicts_with_all = ["path", "branch", "commit"])] - pr: Option, - - /// Custom version name (required for non-git local paths, optional otherwise) - #[arg(short, long)] - name: Option, - - /// Number of CPU cores to use when building - #[arg(short, long)] - jobs: Option, - }, - - /// Update to the latest ampd version (default behavior) - Update { - /// Installation directory (defaults to $AMP_DIR or $XDG_CONFIG_HOME/.amp or $HOME/.amp) - #[arg(long, env = "AMP_DIR")] - install_dir: Option, - - /// GitHub repository in format "owner/repo" - #[arg(long, default_value_t = DEFAULT_REPO.to_string())] - repo: String, - - /// GitHub token for private repository access (defaults to $GITHUB_TOKEN) - #[arg(long, env = "GITHUB_TOKEN", hide_env = true)] - github_token: Option, - - /// Override architecture detection (x86_64, aarch64) - #[arg(long)] - arch: Option, - - /// Override platform detection (linux, darwin) - #[arg(long)] - platform: Option, - }, - - /// Manage the ampup executable - #[command(name = "self")] - SelfCmd { - #[command(subcommand)] - command: SelfCommands, - }, -} - -#[derive(Debug, clap::Subcommand)] -enum SelfCommands { - /// Update ampup itself to the latest version - Update { - /// GitHub repository in format "owner/repo" - #[arg(long, default_value_t = DEFAULT_REPO.to_string())] - repo: String, - - /// GitHub token for private repository access (defaults to $GITHUB_TOKEN) - #[arg(long, env = "GITHUB_TOKEN", hide_env = true)] - github_token: Option, - }, - - /// Print the version of ampup - Version, -} - -#[tokio::main] -async fn main() { - if let Err(e) = run().await { - // Print the error with some custom formatting - eprintln!("{} {}", style("✗").red().bold(), e); - std::process::exit(1); - } -} - -async fn run() -> anyhow::Result<()> { - let cli = ::parse(); - - match cli.command { - Some(Commands::Init { - install_dir, - no_modify_path, - no_install_latest, - github_token, - }) => { - commands::init::run(install_dir, no_modify_path, no_install_latest, github_token) - .await?; - } - Some(Commands::Install { - install_dir, - version, - repo, - github_token, - arch, - platform, - }) => { - commands::install::run(install_dir, repo, github_token, version, arch, platform) - .await?; - } - Some(Commands::List { install_dir }) => { - commands::list::run(install_dir)?; - } - Some(Commands::Use { - install_dir, - version, - }) => { - commands::use_version::run(install_dir, version)?; - } - Some(Commands::Uninstall { - install_dir, - version, - }) => { - commands::uninstall::run(install_dir, &version)?; - } - Some(Commands::Build { - install_dir, - path, - repo, - branch, - commit, - pr, - name, - jobs, - }) => { - commands::build::run(install_dir, repo, path, branch, commit, pr, name, jobs).await?; - } - Some(Commands::Update { - install_dir, - repo, - github_token, - arch, - platform, - }) => { - // Install latest version (same as default behavior) - commands::install::run(install_dir, repo, github_token, None, arch, platform).await?; - } - Some(Commands::SelfCmd { command }) => match command { - SelfCommands::Update { repo, github_token } => { - commands::update::run(repo, github_token).await?; - } - SelfCommands::Version => { - println!("ampup {}", env!("VERGEN_GIT_DESCRIBE")); - } - }, - None => { - // Default: install latest version (same as 'ampup update') - commands::install::run( - std::env::var("AMP_DIR").ok().map(std::path::PathBuf::from), - DEFAULT_REPO.to_string(), - std::env::var("GITHUB_TOKEN").ok(), - None, - None, - None, - ) - .await?; - } - } - - Ok(()) -} diff --git a/crates/bin/ampup/src/platform.rs b/crates/bin/ampup/src/platform.rs deleted file mode 100644 index f058a787c..000000000 --- a/crates/bin/ampup/src/platform.rs +++ /dev/null @@ -1,128 +0,0 @@ -use anyhow::Result; - -#[derive(Debug)] -pub enum PlatformError { - UnsupportedPlatform { detected: String }, - UnsupportedArchitecture { detected: String }, -} - -impl std::fmt::Display for PlatformError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnsupportedPlatform { detected } => { - writeln!(f, "Unsupported platform")?; - writeln!(f, " Detected: {}", detected)?; - writeln!(f, " Supported: linux, macos")?; - writeln!(f)?; - writeln!( - f, - " If you're on a supported platform, this may be a detection issue." - )?; - writeln!(f, " Try using --platform flag to override (linux, darwin)")?; - } - Self::UnsupportedArchitecture { detected } => { - writeln!(f, "Unsupported architecture")?; - writeln!(f, " Detected: {}", detected)?; - writeln!(f, " Supported: x86_64, aarch64 (arm64)")?; - writeln!(f)?; - writeln!( - f, - " If you're on a supported architecture, this may be a detection issue." - )?; - writeln!(f, " Try using --arch flag to override (x86_64, aarch64)")?; - } - } - Ok(()) - } -} - -impl std::error::Error for PlatformError {} - -/// Supported platforms -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Platform { - Linux, - Darwin, -} - -impl Platform { - /// Detect the current platform - pub fn detect() -> Result { - match std::env::consts::OS { - "linux" => Ok(Self::Linux), - "macos" => Ok(Self::Darwin), - os => Err(PlatformError::UnsupportedPlatform { - detected: os.to_string(), - } - .into()), - } - } - - /// Get the platform string for artifact names - pub fn as_str(&self) -> &'static str { - match self { - Self::Linux => "linux", - Self::Darwin => "darwin", - } - } -} - -impl std::fmt::Display for Platform { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -/// Supported architectures -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Architecture { - X86_64, - Aarch64, -} - -impl Architecture { - /// Detect the current architecture - pub fn detect() -> Result { - match std::env::consts::ARCH { - "x86_64" | "amd64" => Ok(Self::X86_64), - "aarch64" | "arm64" => Ok(Self::Aarch64), - arch => Err(PlatformError::UnsupportedArchitecture { - detected: arch.to_string(), - } - .into()), - } - } - - /// Get the architecture string for artifact names - pub fn as_str(&self) -> &'static str { - match self { - Self::X86_64 => "x86_64", - Self::Aarch64 => "aarch64", - } - } -} - -impl std::fmt::Display for Architecture { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_str()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_platform_detect() { - // This will work on any supported platform - let platform = Platform::detect(); - assert!(platform.is_ok()); - } - - #[test] - fn test_arch_detect() { - // This will work on any supported architecture - let arch = Architecture::detect(); - assert!(arch.is_ok()); - } -} diff --git a/crates/bin/ampup/src/shell.rs b/crates/bin/ampup/src/shell.rs deleted file mode 100644 index 97bc06ee7..000000000 --- a/crates/bin/ampup/src/shell.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::path::PathBuf; - -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::ui; - -#[derive(Debug)] -pub enum ShellError { - ShellNotDetected, -} - -impl std::fmt::Display for ShellError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ShellNotDetected => { - writeln!(f, "Could not detect shell type")?; - writeln!(f)?; - writeln!( - f, - " The SHELL environment variable is not set or is unsupported." - )?; - writeln!(f, " Supported shells: bash, zsh, fish, ash")?; - writeln!(f)?; - writeln!( - f, - " You can manually add the following to your shell profile:" - )?; - writeln!(f, " export PATH=\"$PATH:$HOME/.amp/bin\"")?; - } - } - Ok(()) - } -} - -impl std::error::Error for ShellError {} - -#[derive(Debug, Clone, Copy)] -pub enum Shell { - Zsh, - Bash, - Fish, - Ash, -} - -impl Shell { - /// Detect the current shell from the SHELL environment variable - pub fn detect() -> Option { - let shell = std::env::var("SHELL").ok()?; - - if shell.ends_with("/zsh") { - Some(Shell::Zsh) - } else if shell.ends_with("/bash") { - Some(Shell::Bash) - } else if shell.ends_with("/fish") { - Some(Shell::Fish) - } else if shell.ends_with("/ash") { - Some(Shell::Ash) - } else { - None - } - } - - /// Get the profile file path for this shell - pub fn profile_path(&self) -> Result { - let home = std::env::var("HOME") - .or_else(|_| std::env::var("USERPROFILE")) - .context("Could not determine home directory")?; - - let path = match self { - Shell::Zsh => { - let zdotdir = std::env::var("ZDOTDIR").unwrap_or(home); - PathBuf::from(zdotdir).join(".zshenv") - } - Shell::Bash => PathBuf::from(home).join(".bashrc"), - Shell::Fish => PathBuf::from(home).join(".config/fish/config.fish"), - Shell::Ash => PathBuf::from(home).join(".profile"), - }; - - Ok(path) - } - - /// Get the PATH export line for this shell - pub fn path_export_line(&self, bin_dir: &str) -> String { - match self { - Shell::Fish => format!("fish_add_path -a {}", bin_dir), - _ => format!("export PATH=\"$PATH:{}\"", bin_dir), - } - } -} - -/// Add a directory to PATH by modifying the shell profile -pub fn add_to_path(bin_dir: &str) -> Result<()> { - let shell = Shell::detect().ok_or(ShellError::ShellNotDetected)?; - let profile_path = shell.profile_path()?; - let export_line = shell.path_export_line(bin_dir); - - // Ensure parent directory exists - if let Some(parent) = profile_path.parent() { - fs::create_dir_all(parent).context("Failed to create profile directory")?; - } - - // Read existing content or start with empty string - let content = if profile_path.exists() { - fs::read_to_string(&profile_path).context("Failed to read shell profile")? - } else { - String::new() - }; - - // Check if already in PATH - if content.contains(&export_line) { - ui::detail!("{} already in PATH", bin_dir); - return Ok(()); - } - - // Append to file - let new_content = if content.is_empty() { - format!("{}\n", export_line) - } else if content.ends_with('\n') { - format!("{}\n{}\n", content.trim_end(), export_line) - } else { - format!("{}\n\n{}\n", content, export_line) - }; - - fs::write(&profile_path, new_content).context("Failed to write to shell profile")?; - - ui::success!( - "Added {} to PATH in {}", - bin_dir, - ui::path(profile_path.display()) - ); - ui::detail!( - "Run 'source {}' or start a new terminal session", - profile_path.display() - ); - - Ok(()) -} diff --git a/crates/bin/ampup/src/tests/fixtures.rs b/crates/bin/ampup/src/tests/fixtures.rs deleted file mode 100644 index fcc8dc594..000000000 --- a/crates/bin/ampup/src/tests/fixtures.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::path::{Path, PathBuf}; - -use anyhow::{Context, Result}; -use fs_err as fs; -use tempfile::TempDir; - -/// Temporary ampup installation directory for testing. -/// -/// Creates an isolated `.amp` directory structure that is automatically -/// cleaned up when dropped (unless TESTS_KEEP_TEMP_DIRS=1 is set). -pub struct TempInstallDir { - _temp_dir: TempDir, - amp_dir: PathBuf, -} - -impl TempInstallDir { - /// Create a new temporary installation directory. - pub fn new() -> Result { - let temp_dir = TempDir::new().context("Failed to create temporary directory")?; - let amp_dir = temp_dir.path().to_path_buf(); - - // Create the basic directory structure - let bin_dir = amp_dir.join("bin"); - let versions_dir = amp_dir.join("versions"); - - fs::create_dir_all(&bin_dir).context("Failed to create bin directory")?; - fs::create_dir_all(&versions_dir).context("Failed to create versions directory")?; - - Ok(Self { - _temp_dir: temp_dir, - amp_dir, - }) - } - - /// Get the path to the temporary amp directory. - pub fn path(&self) -> &Path { - &self.amp_dir - } - - /// Get the path to the bin directory. - pub fn bin_dir(&self) -> PathBuf { - self.amp_dir.join("bin") - } - - /// Get the path to the versions directory. - pub fn versions_dir(&self) -> PathBuf { - self.amp_dir.join("versions") - } - - /// Get the path to the ampup binary. - pub fn ampup_binary(&self) -> PathBuf { - self.bin_dir().join("ampup") - } - - /// Get the path to the active amp binary symlink. - pub fn active_binary(&self) -> PathBuf { - self.bin_dir().join("ampd") - } - - /// Get the path to the current version file. - pub fn current_version_file(&self) -> PathBuf { - self.amp_dir.join(".version") - } - - /// Get the path to a specific version directory. - pub fn version_dir(&self, version: &str) -> PathBuf { - self.versions_dir().join(version) - } - - /// Get the path to a specific version binary. - pub fn version_binary(&self, version: &str) -> PathBuf { - self.version_dir(version).join("ampd") - } -} - -/// Helper for creating mock ampd and ampctl binaries for testing. -pub struct MockBinary; - -impl MockBinary { - /// Create mock ampd and ampctl binaries for a specific version. - pub fn create(temp: &TempInstallDir, version: &str) -> Result<()> { - let version_dir = temp.version_dir(version); - fs::create_dir_all(&version_dir) - .with_context(|| format!("Failed to create version directory for {}", version))?; - - // Create mock ampd binary - let ampd_path = version_dir.join("ampd"); - let ampd_script = format!("#!/bin/sh\necho 'ampd {}'", version); - fs::write(&d_path, ampd_script) - .with_context(|| format!("Failed to write mock ampd binary for {}", version))?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&d_path) - .context("Failed to get ampd metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&d_path, perms) - .context("Failed to set executable permissions on ampd")?; - } - - // Create mock ampctl binary - let ampctl_path = version_dir.join("ampctl"); - let ampctl_script = format!("#!/bin/sh\necho 'ampctl {}'", version); - fs::write(&ctl_path, ampctl_script) - .with_context(|| format!("Failed to write mock ampctl binary for {}", version))?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&ctl_path) - .context("Failed to get ampctl metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&ctl_path, perms) - .context("Failed to set executable permissions on ampctl")?; - } - - Ok(()) - } -} diff --git a/crates/bin/ampup/src/tests/it_ampup.rs b/crates/bin/ampup/src/tests/it_ampup.rs deleted file mode 100644 index eaaae4e59..000000000 --- a/crates/bin/ampup/src/tests/it_ampup.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::env; - -use anyhow::Result; -use fs_err as fs; -use tempfile::TempDir; - -use super::fixtures::{MockBinary, TempInstallDir}; -use crate::DEFAULT_REPO; - -#[tokio::test] -async fn init_creates_directory_structure() -> Result<()> { - let temp = TempInstallDir::new()?; - - crate::commands::init::run( - Some(temp.path().to_path_buf()), - true, // no_modify_path - true, // no_install_latest - None, // github_token - ) - .await?; - - assert!(temp.ampup_binary().exists(), "ampup binary not created"); - assert!(temp.bin_dir().exists(), "bin directory not created"); - assert!( - temp.versions_dir().exists(), - "versions directory not created" - ); - - Ok(()) -} - -#[tokio::test] -async fn init_fails_if_already_initialized() -> Result<()> { - let temp = TempInstallDir::new()?; - - // First init should succeed - crate::commands::init::run(Some(temp.path().to_path_buf()), true, true, None).await?; - - // Second init should fail - let result = - crate::commands::init::run(Some(temp.path().to_path_buf()), true, true, None).await; - - assert!( - result.is_err(), - "Expected init to fail when already initialized" - ); - assert!( - result - .unwrap_err() - .to_string() - .contains("already initialized") - ); - - Ok(()) -} - -#[tokio::test] -async fn list_shows_no_versions_when_empty() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Just verify it doesn't crash - actual output goes to stdout - crate::commands::list::run(Some(temp.path().to_path_buf()))?; - - Ok(()) -} - -#[tokio::test] -async fn list_shows_installed_versions() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Create mock versions - MockBinary::create(&temp, "v1.0.0")?; - MockBinary::create(&temp, "v1.1.0")?; - - // Set current version - fs::write(temp.current_version_file(), "v1.0.0")?; - - // Just verify it doesn't crash - actual output goes to stdout - crate::commands::list::run(Some(temp.path().to_path_buf()))?; - - Ok(()) -} - -#[tokio::test] -async fn use_switches_to_installed_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Create mock versions first - MockBinary::create(&temp, "v1.0.0")?; - MockBinary::create(&temp, "v1.1.0")?; - - // Switch to v1.0.0 - crate::commands::use_version::run(Some(temp.path().to_path_buf()), Some("v1.0.0".to_string()))?; - - // Verify current version - let current = fs::read_to_string(temp.current_version_file())?; - assert_eq!(current.trim(), "v1.0.0"); - - // Verify symlink points to correct binary - let active_binary = temp.active_binary(); - assert!(active_binary.exists() || active_binary.is_symlink()); - - // Switch to v1.1.0 - crate::commands::use_version::run(Some(temp.path().to_path_buf()), Some("v1.1.0".to_string()))?; - - let current = fs::read_to_string(temp.current_version_file())?; - assert_eq!(current.trim(), "v1.1.0"); - - Ok(()) -} - -#[tokio::test] -async fn use_fails_for_non_existent_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - let result = crate::commands::use_version::run( - Some(temp.path().to_path_buf()), - Some("v99.99.99".to_string()), - ); - - assert!( - result.is_err(), - "Expected use to fail for non-existent version" - ); - assert!(result.unwrap_err().to_string().contains("not installed")); - - Ok(()) -} - -#[tokio::test] -async fn uninstall_removes_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Create mock versions first - MockBinary::create(&temp, "v1.0.0")?; - MockBinary::create(&temp, "v1.1.0")?; - - // Set current version to v1.1.0 - crate::commands::use_version::run(Some(temp.path().to_path_buf()), Some("v1.1.0".to_string()))?; - - // Uninstall v1.0.0 (not current) - crate::commands::uninstall::run(Some(temp.path().to_path_buf()), "v1.0.0")?; - - assert!( - !temp.version_dir("v1.0.0").exists(), - "Version directory should be removed" - ); - assert!( - temp.version_dir("v1.1.0").exists(), - "Other version should still exist" - ); - - Ok(()) -} - -#[tokio::test] -async fn uninstall_fails_for_non_existent_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - let result = crate::commands::uninstall::run(Some(temp.path().to_path_buf()), "v99.99.99"); - - assert!( - result.is_err(), - "Expected uninstall to fail for non-existent version" - ); - - Ok(()) -} - -#[tokio::test] -#[ignore = "Re-enable this and bump versions once the repository is public"] -async fn install_latest_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Install latest version - crate::commands::install::run( - Some(temp.path().to_path_buf()), - DEFAULT_REPO.to_string(), - None, - None, - None, - None, - ) - .await?; - - // Verify a version was installed - let versions: Vec<_> = fs::read_dir(temp.versions_dir())? - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().ok().map(|ft| ft.is_dir()).unwrap_or(false)) - .collect(); - - assert!(!versions.is_empty(), "No version was installed"); - - Ok(()) -} - -#[tokio::test] -#[ignore = "Re-enable this and bump versions once the repository is public"] -async fn install_specific_version() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Install a specific version (use a known release) - let version = "v0.0.21"; - crate::commands::install::run( - Some(temp.path().to_path_buf()), - DEFAULT_REPO.to_string(), - None, - Some(version.to_string()), - None, - None, - ) - .await?; - - assert!( - temp.version_dir(version).exists(), - "Version directory not created" - ); - assert!(temp.version_binary(version).exists(), "Binary not created"); - - Ok(()) -} - -#[tokio::test] -#[ignore = "Re-enable this and bump versions once the repository is public"] -async fn install_already_installed_version_switches_to_it() -> Result<()> { - let temp = TempInstallDir::new()?; - let version = "v0.0.21"; - - // Install once - crate::commands::install::run( - Some(temp.path().to_path_buf()), - DEFAULT_REPO.to_string(), - None, - Some(version.to_string()), - None, - None, - ) - .await?; - - // Install again - should just switch to it - crate::commands::install::run( - Some(temp.path().to_path_buf()), - DEFAULT_REPO.to_string(), - None, - Some(version.to_string()), - None, - None, - ) - .await?; - - let current = fs::read_to_string(temp.current_version_file())?; - assert_eq!(current.trim(), version); - - Ok(()) -} - -#[tokio::test] -async fn build_from_local_path_with_custom_name() -> Result<()> { - let temp = TempInstallDir::new()?; - - // Create a temporary directory that looks like a amp repo - let fake_repo = TempDir::new()?; - let target_dir = fake_repo.path().join("target/release"); - fs::create_dir_all(&target_dir)?; - - // Create mock ampd binary - let mock_ampd = target_dir.join("ampd"); - fs::write(&mock_ampd, "#!/bin/sh\necho 'ampd test-version'")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&mock_ampd)?.permissions(); - perms.set_mode(0o755); - fs::set_permissions(&mock_ampd, perms)?; - } - - // Create mock ampctl binary - let mock_ampctl = target_dir.join("ampctl"); - fs::write(&mock_ampctl, "#!/bin/sh\necho 'ampctl test-version'")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&mock_ampctl)?.permissions(); - perms.set_mode(0o755); - fs::set_permissions(&mock_ampctl, perms)?; - } - - // Mock cargo by creating a fake cargo script - let mock_cargo_dir = TempDir::new()?; - let mock_cargo = mock_cargo_dir.path().join("cargo"); - fs::write(&mock_cargo, "#!/bin/sh\nexit 0")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&mock_cargo)?.permissions(); - perms.set_mode(0o755); - fs::set_permissions(&mock_cargo, perms)?; - } - - // Temporarily modify PATH to use mock cargo - let original_path = env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", mock_cargo_dir.path().display(), original_path); - unsafe { - env::set_var("PATH", &new_path); - } - - let custom_name = "my-custom-build"; - let result = crate::commands::build::run( - Some(temp.path().to_path_buf()), - None, // repo - Some(fake_repo.path().to_path_buf()), - None, // branch - None, // commit - None, // pr - Some(custom_name.to_string()), - None, // jobs - ) - .await; - - // Restore PATH - unsafe { - env::set_var("PATH", &original_path); - } - - result?; - - assert!( - temp.version_dir(custom_name).exists(), - "Custom version not created" - ); - assert!( - temp.version_binary(custom_name).exists(), - "Binary not installed" - ); - - Ok(()) -} diff --git a/crates/bin/ampup/src/tests/mod.rs b/crates/bin/ampup/src/tests/mod.rs deleted file mode 100644 index f43f76c8e..000000000 --- a/crates/bin/ampup/src/tests/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod fixtures; -mod it_ampup; diff --git a/crates/bin/ampup/src/ui.rs b/crates/bin/ampup/src/ui.rs deleted file mode 100644 index 7b8898242..000000000 --- a/crates/bin/ampup/src/ui.rs +++ /dev/null @@ -1,44 +0,0 @@ -use console::style; - -/// Print a success message with a green checkmark -macro_rules! success { - ($($arg:tt)*) => { - println!("{} {}", console::style("✓").green().bold(), format!($($arg)*)) - }; -} - -/// Print an info message with a cyan arrow -macro_rules! info { - ($($arg:tt)*) => { - println!("{} {}", console::style("→").cyan(), format!($($arg)*)) - }; -} - -/// Print a warning message with a yellow warning symbol -macro_rules! warning { - ($($arg:tt)*) => { - eprintln!("{} {}", console::style("⚠").yellow().bold(), format!($($arg)*)) - }; -} - -/// Print a dimmed detail message (indented) -macro_rules! detail { - ($($arg:tt)*) => { - println!(" {}", console::style(format!($($arg)*)).dim()) - }; -} - -pub(crate) use detail; -pub(crate) use info; -pub(crate) use success; -pub(crate) use warning as warn; - -/// Style a version string (bold white) -pub fn version(v: impl std::fmt::Display) -> String { - style(v).bold().to_string() -} - -/// Style a path (cyan) -pub fn path(p: impl std::fmt::Display) -> String { - style(p).cyan().to_string() -} diff --git a/crates/bin/ampup/src/updater.rs b/crates/bin/ampup/src/updater.rs deleted file mode 100644 index cdf48b465..000000000 --- a/crates/bin/ampup/src/updater.rs +++ /dev/null @@ -1,74 +0,0 @@ -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::{ - github::GitHubClient, - platform::{Architecture, Platform}, - ui, -}; - -/// Handles self-updating of ampup -pub struct Updater { - github: GitHubClient, -} - -impl Updater { - /// Create a new updater - pub fn new(github: GitHubClient) -> Self { - Self { github } - } - - /// Get the current version - pub fn get_current_version(&self) -> String { - env!("VERGEN_GIT_DESCRIBE").to_string() - } - - /// Get the latest version - pub async fn get_latest_version(&self) -> Result { - self.github.get_latest_version().await - } - - /// Update ampup to a specific version - pub async fn update_self(&self, version: &str) -> Result<()> { - // Detect platform and architecture - let platform = Platform::detect()?; - let arch = Architecture::detect()?; - - // Download the ampup binary - let artifact_name = format!("ampup-{}-{}", platform.as_str(), arch.as_str()); - ui::info!("Downloading {}", artifact_name); - - let binary_data = self - .github - .download_release_asset(version, &artifact_name) - .await - .context("Failed to download ampup binary")?; - - // Get the current executable path - let current_exe = - std::env::current_exe().context("Failed to get current executable path")?; - - // Write to a temporary file first - let temp_path = current_exe.with_extension("tmp"); - fs::write(&temp_path, &binary_data).context("Failed to write temporary file")?; - - // Make it executable - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(&temp_path) - .context("Failed to get temp file metadata")? - .permissions(); - perms.set_mode(0o755); - fs::set_permissions(&temp_path, perms) - .context("Failed to set executable permissions")?; - } - - // Replace the current executable - fs::rename(&temp_path, ¤t_exe).context("Failed to replace executable")?; - - ui::success!("Updated to {}", ui::version(version)); - - Ok(()) - } -} diff --git a/crates/bin/ampup/src/version_manager.rs b/crates/bin/ampup/src/version_manager.rs deleted file mode 100644 index dfef6a286..000000000 --- a/crates/bin/ampup/src/version_manager.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::os::unix::fs::symlink; - -use anyhow::{Context, Result}; -use fs_err as fs; - -use crate::config::Config; - -/// Version management errors -#[derive(Debug)] -pub enum VersionError { - NotInstalled { version: String }, - NoVersionsInstalled, - BinaryNotFound { version: String }, -} - -impl std::fmt::Display for VersionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::NotInstalled { version } => { - writeln!(f, "Version not installed")?; - writeln!(f, " Version: {}", version)?; - writeln!(f)?; - writeln!(f, " Try: ampup install {}", version)?; - } - Self::NoVersionsInstalled => { - writeln!(f, "No versions installed")?; - writeln!(f)?; - writeln!(f, " Try: ampup install")?; - } - Self::BinaryNotFound { version } => { - writeln!(f, "Binary not found")?; - writeln!(f, " Version: {}", version)?; - writeln!(f)?; - writeln!(f, " Installation may be corrupted.")?; - writeln!(f, " Try: ampup install {}", version)?; - } - } - Ok(()) - } -} - -impl std::error::Error for VersionError {} - -/// Manages installed ampd versions -pub struct VersionManager { - config: Config, -} - -impl VersionManager { - /// Create a new version manager - pub fn new(config: Config) -> Self { - Self { config } - } - - /// Get the configuration - pub fn config(&self) -> &Config { - &self.config - } - - /// List all installed versions, sorted alphabetically - pub fn list_installed(&self) -> Result> { - if !self.config.versions_dir.exists() { - return Ok(Vec::new()); - } - - let mut versions = Vec::new(); - for entry in - fs::read_dir(&self.config.versions_dir).context("Failed to read versions directory")? - { - let entry = entry.context("Failed to read directory entry")?; - if entry - .file_type() - .context("Failed to get file type")? - .is_dir() - { - let version = entry.file_name().to_string_lossy().to_string(); - versions.push(version); - } - } - - versions.sort(); - Ok(versions) - } - - /// Get the currently active version - pub fn get_current(&self) -> Result> { - self.config.current_version() - } - - /// Check if a version is installed - pub fn is_installed(&self, version: &str) -> bool { - self.config.version_binary_path(version).exists() - } - - /// Activate a specific version by creating symlinks and updating version file - pub fn activate(&self, version: &str) -> Result<()> { - let version_dir = self.config.versions_dir.join(version); - if !version_dir.exists() { - return Err(VersionError::NotInstalled { - version: version.to_string(), - } - .into()); - } - - let ampd_binary_path = self.config.version_binary_path(version); - if !ampd_binary_path.exists() { - return Err(VersionError::BinaryNotFound { - version: version.to_string(), - } - .into()); - } - - let ampctl_binary_path = self.config.version_ampctl_path(version); - if !ampctl_binary_path.exists() { - return Err(VersionError::BinaryNotFound { - version: version.to_string(), - } - .into()); - } - - // Handle ampd symlink - let ampd_active_path = self.config.active_binary_path(); - if ampd_active_path.exists() || ampd_active_path.is_symlink() { - fs::remove_file(&d_active_path).context("Failed to remove existing ampd symlink")?; - } - symlink(&d_binary_path, &d_active_path).context("Failed to create ampd symlink")?; - - // Handle ampctl symlink - let ampctl_active_path = self.config.active_ampctl_path(); - if ampctl_active_path.exists() || ampctl_active_path.is_symlink() { - fs::remove_file(&ctl_active_path) - .context("Failed to remove existing ampctl symlink")?; - } - symlink(&ctl_binary_path, &ctl_active_path) - .context("Failed to create ampctl symlink")?; - - // Update current version file - self.config.set_current_version(version)?; - - Ok(()) - } - - /// Uninstall a specific version - pub fn uninstall(&self, version: &str) -> Result<()> { - let version_dir = self.config.versions_dir.join(version); - if !version_dir.exists() { - return Err(VersionError::NotInstalled { - version: version.to_string(), - } - .into()); - } - - // Check if this is the current version - let current = self.get_current()?; - let is_current = current.as_deref() == Some(version); - - // Remove the version directory - fs::remove_dir_all(&version_dir).context("Failed to remove version directory")?; - - // If this was the current version, clear the current version file and symlinks - if is_current { - let current_file = self.config.current_version_file(); - if current_file.exists() { - fs::remove_file(¤t_file).context("Failed to remove current version file")?; - } - - // Remove the ampd symlink - let ampd_active_path = self.config.active_binary_path(); - if ampd_active_path.exists() || ampd_active_path.is_symlink() { - fs::remove_file(&d_active_path).context("Failed to remove ampd symlink")?; - } - - // Remove the ampctl symlink - let ampctl_active_path = self.config.active_ampctl_path(); - if ampctl_active_path.exists() || ampctl_active_path.is_symlink() { - fs::remove_file(&ctl_active_path).context("Failed to remove ampctl symlink")?; - } - } - - Ok(()) - } -} diff --git a/crates/core/monitoring/src/logging.rs b/crates/core/monitoring/src/logging.rs index ad53febd3..7b2e283e2 100644 --- a/crates/core/monitoring/src/logging.rs +++ b/crates/core/monitoring/src/logging.rs @@ -82,7 +82,6 @@ const AMP_CRATES: &[&str] = &[ "ampctl", "ampd", "ampsync", - "ampup", "arrow_to_postgres", "auth_http", "common", diff --git a/docs/exes.md b/docs/exes.md index c2399697f..a97e325e4 100644 --- a/docs/exes.md +++ b/docs/exes.md @@ -6,61 +6,11 @@ This document provides an overview of the executables available in the Amp proje Amp provides several executables designed for different use cases and user personas: -- **`ampup`** - Toolchain distribution and version management - **`ampd`** - The Amp daemon (extraction, query serving, job orchestration) - **`ampctl`** - Administration and control CLI for operators - **`amp`** - Developer toolkit CLI for dataset development - **`ampsync`** - PostgreSQL synchronization utility -## `ampup` - Toolchain Distribution and Management - -**Purpose**: `ampup` is the official installer and version manager for the Amp toolchain. It handles downloading, installing, and switching between different versions of `ampd` and related binaries. - -**Installation**: Get started with Amp by running the installation script from [ampup.sh](http://ampup.sh): - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://ampup.sh/install | sh -``` - -This script downloads and runs `ampup init`, which: - -- Sets up the Amp installation directory (`$AMP_DIR` or `~/.amp`) -- Configures your shell's PATH -- Installs the latest version of `ampd` by default - -**Key Features**: - -- **Version management**: Install, list, switch between, and uninstall versions -- **Binary distribution**: Downloads pre-built binaries from GitHub releases -- **Build from source**: Can build and install from local or remote Git repositories -- **Cross-platform**: Supports Linux and macOS (x86_64 and aarch64) - -**When to Use**: - -- **Initial setup**: Setting up Amp on a new system -- **Version management**: Upgrading or downgrading Amp versions -- **Testing**: Managing multiple Amp versions for testing or compatibility -- **Production deployments**: Installing specific versions for production environments - -**Common Commands**: - -```bash -# Install latest version -ampup install - -# Install specific version -ampup install v0.5.0 - -# List installed versions -ampup list - -# Switch to a different version -ampup use v0.4.0 - -# Build from source -ampup build --path /path/to/amp-repo -``` - ## Main Executables ### `ampd` - The Amp Daemon diff --git a/justfile b/justfile index 3c8023832..299f579da 100644 --- a/justfile +++ b/justfile @@ -126,7 +126,7 @@ test-unit *EXTRA_FLAGS: set -e # Exit on error if command -v "cargo-nextest" &> /dev/null; then - cargo nextest run {{EXTRA_FLAGS}} --workspace --exclude tests --exclude ampup --all-features + cargo nextest run {{EXTRA_FLAGS}} --workspace --exclude tests --all-features else >&2 echo "=================================================================" >&2 echo "WARNING: cargo-nextest not found - using 'cargo test' fallback ⚠️" @@ -135,7 +135,7 @@ test-unit *EXTRA_FLAGS: >&2 echo " cargo install --locked cargo-nextest@^0.9" >&2 echo "=================================================================" sleep 1 # Give the user a moment to read the warning - cargo test {{EXTRA_FLAGS}} --workspace --exclude tests --exclude ampup --all-features -- --nocapture + cargo test {{EXTRA_FLAGS}} --workspace --exclude tests --all-features -- --nocapture fi # Run integration tests @@ -175,25 +175,6 @@ test-local *EXTRA_FLAGS: exit 1 fi -# Run ampup tests -[group: 'test'] -test-ampup *EXTRA_FLAGS: - #!/usr/bin/env bash - set -e # Exit on error - - if command -v "cargo-nextest" &> /dev/null; then - cargo nextest run {{EXTRA_FLAGS}} --package ampup - else - >&2 echo "=================================================================" - >&2 echo "WARNING: cargo-nextest not found - using 'cargo test' fallback ⚠️" - >&2 echo "" - >&2 echo "For faster test execution, consider installing cargo-nextest:" - >&2 echo " cargo install --locked cargo-nextest@^0.9" - >&2 echo "=================================================================" - sleep 1 # Give the user a moment to read the warning - cargo test {{EXTRA_FLAGS}} --package ampup --test it_ampup -- --nocapture - fi - ## Codegen