From 8d6a6eb4e2e87c5854f63086d2111d8121abea56 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 10:15:50 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=89=E8=A3=85=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E5=88=B0=20bee-=20=E9=81=97=E7=95=99=20Relea?= =?UTF-8?q?se=20=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1.16.0 GitHub Release 资产仍是 bee- 前缀,包内二进制名为 bee。 install.sh / install.ps1 先试 amber-,404 再回退 bee-,并统一安装为 amber。 Co-authored-by: Henry Zhang --- .github/workflows/release-assets.yml | 2 + apps/website/README.md | 9 + apps/website/public/install.ps1 | 40 ++- apps/website/public/install.sh | 76 +++++- install.ps1 | 40 ++- install.sh | 76 +++++- tests/install_script_fallback_tests.rs | 322 +++++++++++++++++++++++++ tests/release_workflow_tests.rs | 4 + 8 files changed, 531 insertions(+), 38 deletions(-) create mode 100644 tests/install_script_fallback_tests.rs diff --git a/.github/workflows/release-assets.yml b/.github/workflows/release-assets.yml index a55789e1..51fb5423 100644 --- a/.github/workflows/release-assets.yml +++ b/.github/workflows/release-assets.yml @@ -122,6 +122,8 @@ jobs: run: | mkdir -p staging src="target/${TARGET}/release/${BIN}" + # Prefix must stay amber-. v1.16.0 and earlier shipped bee- leftovers; + # install.sh / install.ps1 fall back to that name for old tags. if [ "${ARCHIVE}" = "zip" ]; then cp "${src}" staging/amber.exe python3 -c "import shutil; shutil.make_archive('amber-${RELEASE_TAG}-${TARGET}', 'zip', 'staging')" diff --git a/apps/website/README.md b/apps/website/README.md index d0c536d3..c732db24 100644 --- a/apps/website/README.md +++ b/apps/website/README.md @@ -19,9 +19,18 @@ The production bundle is written to `dist/`. The `prebuild` step copies repo-roo ## Cloudflare Deploy +`get.amberjs.com` is a custom domain on the same Worker as `amberjs.com` (`apps/website/wrangler.toml`). There is no GitHub Actions deploy job; publishing is a local Wrangler deploy (needs a Cloudflare account with access to this Worker): + ```bash pnpm run deploy:dry-run pnpm run deploy ``` `wrangler.toml` serves `dist/` as static assets and uses `single-page-application` fallback so direct links such as `/docs/installation` work on Cloudflare. + +`src/worker.ts` serves `/install.sh` and `/install.ps1` from the R2 bucket `amber-dist` **when that object exists**, otherwise from the Worker static assets. Live `get.amberjs.com/install.sh` currently matches the asset-hosted copy (`content-type: application/x-sh`, `max-age=0`), so a Wrangler deploy updates the installer. If R2 later has `install.sh` / `install.ps1`, it shadows the deploy — also upload: + +```bash +npx wrangler r2 object put install.sh --file=../../install.sh --bucket=amber-dist --remote --content-type text/plain +npx wrangler r2 object put install.ps1 --file=../../install.ps1 --bucket=amber-dist --remote --content-type text/plain +``` diff --git a/apps/website/public/install.ps1 b/apps/website/public/install.ps1 index e0cafb83..f0c4b080 100644 --- a/apps/website/public/install.ps1 +++ b/apps/website/public/install.ps1 @@ -1,4 +1,4 @@ -# Amber Windows installer. Downloads amber-vX-x86_64-pc-windows-msvc.zip from GitHub Releases. +# Amber Windows installer. Tries amber-vX-x86_64-pc-windows-msvc.zip, then legacy bee- zip. param( [string]$Version = $env:AMBER_VERSION, [string]$InstallDir = $(if ($env:AMBER_INSTALL_DIR) { $env:AMBER_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "amber\bin" }), @@ -17,12 +17,36 @@ if ($Version -notmatch '^v') { $Version = "v$Version" } -$asset = "amber-$Version-$Target.zip" -$url = "https://github.com/$Repo/releases/download/$Version/$asset" -$tmp = Join-Path ([System.IO.Path]::GetTempPath()) $asset +if ($env:AMBER_RELEASE_BASE) { + $releaseRoot = $env:AMBER_RELEASE_BASE.TrimEnd("/") + "/$Version" +} else { + $releaseRoot = "https://github.com/$Repo/releases/download/$Version" +} + +$urls = @( + "$releaseRoot/amber-$Version-$Target.zip", + "$releaseRoot/bee-$Version-$Target.zip" +) + +$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("amber-" + [guid]::NewGuid().ToString("n") + ".zip") +$downloaded = $false +$tried = New-Object System.Collections.Generic.List[string] -Write-Host "Downloading $url" -Invoke-WebRequest -Uri $url -OutFile $tmp +foreach ($url in $urls) { + [void]$tried.Add($url) + Write-Host "Downloading $url" + try { + Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing + $downloaded = $true + break + } catch { + Write-Host "amber install: not found: $url" + } +} + +if (-not $downloaded) { + throw "amber install: download failed (tried: $($tried -join ' '))" +} $extract = Join-Path ([System.IO.Path]::GetTempPath()) ("amber-" + [guid]::NewGuid().ToString("n")) New-Item -ItemType Directory -Path $extract | Out-Null @@ -30,10 +54,10 @@ Expand-Archive -Path $tmp -DestinationPath $extract -Force $src = Get-ChildItem -Path $extract -Recurse -Filter "amber.exe" | Select-Object -First 1 if (-not $src) { - $src = Get-ChildItem -Path $extract -Recurse -Filter "amber.exe" | Select-Object -First 1 + $src = Get-ChildItem -Path $extract -Recurse -Filter "bee.exe" | Select-Object -First 1 } if (-not $src) { - throw "amber.exe not found in archive" + throw "amber.exe or bee.exe not found in archive" } New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null diff --git a/apps/website/public/install.sh b/apps/website/public/install.sh index 0893a360..bb70fc1a 100755 --- a/apps/website/public/install.sh +++ b/apps/website/public/install.sh @@ -19,6 +19,11 @@ Environment variables: AMBER_INSTALL_DIR Install directory (default: ~/.amber/bin) AMBER_REPO GitHub repo (default: zh30/amberjs) +The installer tries amber--.tar.gz first, then the +legacy bee--.tar.gz asset from older releases. The +archive may contain a binary named amber or bee; it is always +installed as $AMBER_INSTALL_DIR/amber. + Examples: AMBER_VERSION=v1.16.0 sh install.sh AMBER_INSTALL_DIR=~/.local/bin sh install.sh @@ -42,9 +47,14 @@ need_cmd() { if need_cmd curl; then http_get() { curl -fsSL "$1"; } http_download() { curl -fsSL "$1" -o "$2"; } + http_try_download() { + code=$(curl -sSL -o "$2" -w "%{http_code}" "$1") || return 1 + [ "$code" = "200" ] + } elif need_cmd wget; then http_get() { wget -qO- "$1"; } http_download() { wget -qO "$2" "$1"; } + http_try_download() { wget -qO "$2" "$1"; } else fail "curl or wget is required" fi @@ -73,6 +83,23 @@ if [ "${1:-}" = "--print-platform" ]; then exit 0 fi +release_asset_url() { + version_tag="$1" + filename="$2" + if [ -n "${AMBER_RELEASE_BASE:-}" ]; then + echo "${AMBER_RELEASE_BASE%/}/${version_tag}/${filename}" + else + echo "https://github.com/${AMBER_REPO}/releases/download/${version_tag}/${filename}" + fi +} + +candidate_archive_urls() { + version_tag="$1" + target="$2" + release_asset_url "$version_tag" "amber-${version_tag}-${target}.tar.gz" + release_asset_url "$version_tag" "bee-${version_tag}-${target}.tar.gz" +} + resolve_version() { if [ -n "${AMBER_VERSION:-}" ]; then version="${AMBER_VERSION}" @@ -89,30 +116,57 @@ resolve_version() { esac } +if [ "${1:-}" = "--print-asset-urls" ]; then + print_target=$(resolve_platform) + print_tag=$(resolve_version) + candidate_archive_urls "$print_tag" "$print_target" + exit 0 +fi + +find_extracted_binary() { + root="$1" + if [ -f "$root/amber" ]; then + echo "$root/amber" + return 0 + fi + if [ -f "$root/bee" ]; then + echo "$root/bee" + return 0 + fi + find "$root" -type f \( -name amber -o -name bee \) | head -n 1 +} + install_binary() { target="$1" version_tag="$2" tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t amber) archive="$tmpdir/amber.tar.gz" - url="https://github.com/${AMBER_REPO}/releases/download/${version_tag}/amber-${version_tag}-${target}.tar.gz" trap 'rm -rf "$tmpdir"' EXIT INT TERM - echo "Downloading ${url}" - http_download "$url" "$archive" || fail "download failed" + amber_url=$(release_asset_url "$version_tag" "amber-${version_tag}-${target}.tar.gz") + bee_url=$(release_asset_url "$version_tag" "bee-${version_tag}-${target}.tar.gz") + tried="${amber_url} ${bee_url}" + downloaded="" - tar -xzf "$archive" -C "$tmpdir" || fail "failed to extract archive" - - if [ -f "$tmpdir/amber" ]; then - src="$tmpdir/amber" - elif [ -f "$tmpdir/amber" ]; then - src="$tmpdir/amber" + # Prefer amber- assets; fall back to bee- leftovers from v1.16.0 and earlier. + echo "Downloading ${amber_url}" + if http_try_download "$amber_url" "$archive"; then + downloaded="$amber_url" else - src=$(find "$tmpdir" -type f \( -name amber -o -name amber \) | head -n 1) + echo "Downloading ${bee_url}" + if http_try_download "$bee_url" "$archive"; then + downloaded="$bee_url" + fi fi - [ -n "${src:-}" ] || fail "amber binary not found in archive" + [ -n "$downloaded" ] || fail "download failed (tried: ${tried})" + + tar -xzf "$archive" -C "$tmpdir" || fail "failed to extract archive" + + src=$(find_extracted_binary "$tmpdir") + [ -n "${src:-}" ] || fail "amber or bee binary not found in archive" mkdir -p "$AMBER_INSTALL_DIR" cp "$src" "$AMBER_INSTALL_DIR/amber" diff --git a/install.ps1 b/install.ps1 index e0cafb83..f0c4b080 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,4 +1,4 @@ -# Amber Windows installer. Downloads amber-vX-x86_64-pc-windows-msvc.zip from GitHub Releases. +# Amber Windows installer. Tries amber-vX-x86_64-pc-windows-msvc.zip, then legacy bee- zip. param( [string]$Version = $env:AMBER_VERSION, [string]$InstallDir = $(if ($env:AMBER_INSTALL_DIR) { $env:AMBER_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "amber\bin" }), @@ -17,12 +17,36 @@ if ($Version -notmatch '^v') { $Version = "v$Version" } -$asset = "amber-$Version-$Target.zip" -$url = "https://github.com/$Repo/releases/download/$Version/$asset" -$tmp = Join-Path ([System.IO.Path]::GetTempPath()) $asset +if ($env:AMBER_RELEASE_BASE) { + $releaseRoot = $env:AMBER_RELEASE_BASE.TrimEnd("/") + "/$Version" +} else { + $releaseRoot = "https://github.com/$Repo/releases/download/$Version" +} + +$urls = @( + "$releaseRoot/amber-$Version-$Target.zip", + "$releaseRoot/bee-$Version-$Target.zip" +) + +$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("amber-" + [guid]::NewGuid().ToString("n") + ".zip") +$downloaded = $false +$tried = New-Object System.Collections.Generic.List[string] -Write-Host "Downloading $url" -Invoke-WebRequest -Uri $url -OutFile $tmp +foreach ($url in $urls) { + [void]$tried.Add($url) + Write-Host "Downloading $url" + try { + Invoke-WebRequest -Uri $url -OutFile $tmp -UseBasicParsing + $downloaded = $true + break + } catch { + Write-Host "amber install: not found: $url" + } +} + +if (-not $downloaded) { + throw "amber install: download failed (tried: $($tried -join ' '))" +} $extract = Join-Path ([System.IO.Path]::GetTempPath()) ("amber-" + [guid]::NewGuid().ToString("n")) New-Item -ItemType Directory -Path $extract | Out-Null @@ -30,10 +54,10 @@ Expand-Archive -Path $tmp -DestinationPath $extract -Force $src = Get-ChildItem -Path $extract -Recurse -Filter "amber.exe" | Select-Object -First 1 if (-not $src) { - $src = Get-ChildItem -Path $extract -Recurse -Filter "amber.exe" | Select-Object -First 1 + $src = Get-ChildItem -Path $extract -Recurse -Filter "bee.exe" | Select-Object -First 1 } if (-not $src) { - throw "amber.exe not found in archive" + throw "amber.exe or bee.exe not found in archive" } New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null diff --git a/install.sh b/install.sh index 0893a360..bb70fc1a 100755 --- a/install.sh +++ b/install.sh @@ -19,6 +19,11 @@ Environment variables: AMBER_INSTALL_DIR Install directory (default: ~/.amber/bin) AMBER_REPO GitHub repo (default: zh30/amberjs) +The installer tries amber--.tar.gz first, then the +legacy bee--.tar.gz asset from older releases. The +archive may contain a binary named amber or bee; it is always +installed as $AMBER_INSTALL_DIR/amber. + Examples: AMBER_VERSION=v1.16.0 sh install.sh AMBER_INSTALL_DIR=~/.local/bin sh install.sh @@ -42,9 +47,14 @@ need_cmd() { if need_cmd curl; then http_get() { curl -fsSL "$1"; } http_download() { curl -fsSL "$1" -o "$2"; } + http_try_download() { + code=$(curl -sSL -o "$2" -w "%{http_code}" "$1") || return 1 + [ "$code" = "200" ] + } elif need_cmd wget; then http_get() { wget -qO- "$1"; } http_download() { wget -qO "$2" "$1"; } + http_try_download() { wget -qO "$2" "$1"; } else fail "curl or wget is required" fi @@ -73,6 +83,23 @@ if [ "${1:-}" = "--print-platform" ]; then exit 0 fi +release_asset_url() { + version_tag="$1" + filename="$2" + if [ -n "${AMBER_RELEASE_BASE:-}" ]; then + echo "${AMBER_RELEASE_BASE%/}/${version_tag}/${filename}" + else + echo "https://github.com/${AMBER_REPO}/releases/download/${version_tag}/${filename}" + fi +} + +candidate_archive_urls() { + version_tag="$1" + target="$2" + release_asset_url "$version_tag" "amber-${version_tag}-${target}.tar.gz" + release_asset_url "$version_tag" "bee-${version_tag}-${target}.tar.gz" +} + resolve_version() { if [ -n "${AMBER_VERSION:-}" ]; then version="${AMBER_VERSION}" @@ -89,30 +116,57 @@ resolve_version() { esac } +if [ "${1:-}" = "--print-asset-urls" ]; then + print_target=$(resolve_platform) + print_tag=$(resolve_version) + candidate_archive_urls "$print_tag" "$print_target" + exit 0 +fi + +find_extracted_binary() { + root="$1" + if [ -f "$root/amber" ]; then + echo "$root/amber" + return 0 + fi + if [ -f "$root/bee" ]; then + echo "$root/bee" + return 0 + fi + find "$root" -type f \( -name amber -o -name bee \) | head -n 1 +} + install_binary() { target="$1" version_tag="$2" tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t amber) archive="$tmpdir/amber.tar.gz" - url="https://github.com/${AMBER_REPO}/releases/download/${version_tag}/amber-${version_tag}-${target}.tar.gz" trap 'rm -rf "$tmpdir"' EXIT INT TERM - echo "Downloading ${url}" - http_download "$url" "$archive" || fail "download failed" + amber_url=$(release_asset_url "$version_tag" "amber-${version_tag}-${target}.tar.gz") + bee_url=$(release_asset_url "$version_tag" "bee-${version_tag}-${target}.tar.gz") + tried="${amber_url} ${bee_url}" + downloaded="" - tar -xzf "$archive" -C "$tmpdir" || fail "failed to extract archive" - - if [ -f "$tmpdir/amber" ]; then - src="$tmpdir/amber" - elif [ -f "$tmpdir/amber" ]; then - src="$tmpdir/amber" + # Prefer amber- assets; fall back to bee- leftovers from v1.16.0 and earlier. + echo "Downloading ${amber_url}" + if http_try_download "$amber_url" "$archive"; then + downloaded="$amber_url" else - src=$(find "$tmpdir" -type f \( -name amber -o -name amber \) | head -n 1) + echo "Downloading ${bee_url}" + if http_try_download "$bee_url" "$archive"; then + downloaded="$bee_url" + fi fi - [ -n "${src:-}" ] || fail "amber binary not found in archive" + [ -n "$downloaded" ] || fail "download failed (tried: ${tried})" + + tar -xzf "$archive" -C "$tmpdir" || fail "failed to extract archive" + + src=$(find_extracted_binary "$tmpdir") + [ -n "${src:-}" ] || fail "amber or bee binary not found in archive" mkdir -p "$AMBER_INSTALL_DIR" cp "$src" "$AMBER_INSTALL_DIR/amber" diff --git a/tests/install_script_fallback_tests.rs b/tests/install_script_fallback_tests.rs new file mode 100644 index 00000000..55123721 --- /dev/null +++ b/tests/install_script_fallback_tests.rs @@ -0,0 +1,322 @@ +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::{Child, Command, Stdio}; + +fn repo_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +fn install_sh() -> PathBuf { + repo_root().join("install.sh") +} + +fn write_executable(path: &Path, body: &str) { + fs::write(path, body).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(path).unwrap().permissions(); + perms.set_mode(0o755); + fs::set_permissions(path, perms).unwrap(); + } +} + +fn make_tarball(dir: &Path, bin_name: &str, contents: &str, dest: &Path) { + write_executable(&dir.join(bin_name), contents); + let status = Command::new("tar") + .args(["-czf"]) + .arg(dest) + .arg("-C") + .arg(dir) + .arg(bin_name) + .status() + .expect("tar"); + assert!(status.success(), "tar -czf failed"); +} + +struct FixtureServer { + child: Child, + base: String, +} + +impl Drop for FixtureServer { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} + +fn spawn_fixture_server(root: &Path) -> FixtureServer { + let mut child = Command::new("python3") + .arg("-c") + .arg( + r#" +import http.server, os, sys +os.chdir(sys.argv[1]) +httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), http.server.SimpleHTTPRequestHandler) +print(httpd.server_address[1], flush=True) +httpd.serve_forever() +"#, + ) + .arg(root) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .expect("python3 http server"); + + let stdout = child.stdout.take().expect("server stdout"); + let mut ready = BufReader::new(stdout); + let mut line = String::new(); + if ready.read_line(&mut line).unwrap_or(0) == 0 { + let _ = child.kill(); + panic!("fixture server exited before publishing its port"); + } + let port: u16 = line + .trim() + .parse() + .unwrap_or_else(|_| panic!("invalid fixture port: {line:?}")); + + FixtureServer { + child, + base: format!("http://127.0.0.1:{port}"), + } +} + +fn run_install( + version: &str, + os: &str, + arch: &str, + release_base: &str, + install_dir: &Path, + home: &Path, +) -> std::process::Output { + Command::new("sh") + .arg(install_sh()) + .env("AMBER_VERSION", version) + .env("AMBER_UNAME_S", os) + .env("AMBER_UNAME_M", arch) + .env("AMBER_RELEASE_BASE", release_base) + .env("AMBER_INSTALL_DIR", install_dir) + .env("HOME", home) + .env( + "PATH", + format!( + "{}:{}", + install_dir.display(), + std::env::var("PATH").unwrap_or_default() + ), + ) + .output() + .expect("run install.sh") +} + +#[test] +fn print_asset_urls_prefers_amber_then_bee_for_apple_silicon() { + let output = Command::new("sh") + .arg(install_sh()) + .arg("--print-asset-urls") + .env("AMBER_VERSION", "v1.16.0") + .env("AMBER_UNAME_S", "Darwin") + .env("AMBER_UNAME_M", "arm64") + .env_remove("AMBER_RELEASE_BASE") + .output() + .expect("print-asset-urls"); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "print-asset-urls failed: {stderr}{stdout}" + ); + let lines: Vec<&str> = stdout.lines().collect(); + assert_eq!( + lines, + [ + "https://github.com/zh30/amberjs/releases/download/v1.16.0/amber-v1.16.0-aarch64-apple-darwin.tar.gz", + "https://github.com/zh30/amberjs/releases/download/v1.16.0/bee-v1.16.0-aarch64-apple-darwin.tar.gz", + ] + ); +} + +#[test] +fn website_public_installers_match_repo_root() { + for name in ["install.sh", "install.ps1"] { + let root = fs::read_to_string(repo_root().join(name)).unwrap(); + let public = + fs::read_to_string(repo_root().join("apps/website/public").join(name)).unwrap(); + assert_eq!( + root, public, + "{name} website public copy drifted from repo root" + ); + } +} + +#[test] +fn install_scripts_document_bee_fallback_and_final_amber_name() { + let sh = fs::read_to_string(install_sh()).unwrap(); + assert!(sh.contains("bee-${version_tag}-${target}.tar.gz")); + assert!(sh.contains("amber-${version_tag}-${target}.tar.gz")); + assert!(sh.contains("-name bee")); + assert!( + sh.contains(r#"AMBER_INSTALL_DIR/amber"#) || sh.contains(r#"$AMBER_INSTALL_DIR/amber"#) + ); + assert!(sh.contains("download failed (tried:")); + assert!(sh.contains("set -e")); + + let ps1 = fs::read_to_string(repo_root().join("install.ps1")).unwrap(); + assert!(ps1.contains("amber-$Version-$Target.zip")); + assert!(ps1.contains("bee-$Version-$Target.zip")); + assert!(ps1.contains("bee.exe")); + assert!(ps1.contains("amber.exe")); + assert!(ps1.contains("download failed (tried:")); +} + +#[test] +fn install_sh_falls_back_to_bee_archive_and_installs_as_amber() { + let tmp = tempfile::tempdir().unwrap(); + let www = tmp.path().join("www"); + let version_dir = www.join("v1.16.0"); + fs::create_dir_all(&version_dir).unwrap(); + + let stage = tmp.path().join("stage-bee"); + fs::create_dir_all(&stage).unwrap(); + make_tarball( + &stage, + "bee", + "#!/bin/sh\necho bee-payload\n", + &version_dir.join("bee-v1.16.0-aarch64-apple-darwin.tar.gz"), + ); + + let server = spawn_fixture_server(&www); + let home = tmp.path().join("home"); + let install_dir = tmp.path().join("bin"); + fs::create_dir_all(&home).unwrap(); + + let output = run_install( + "v1.16.0", + "Darwin", + "arm64", + &server.base, + &install_dir, + &home, + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "install.sh fallback failed: {stderr}{stdout}" + ); + assert!( + stdout.contains("bee-v1.16.0-aarch64-apple-darwin.tar.gz"), + "should download bee- URL after amber- 404: {stdout}" + ); + assert!( + stdout.contains("amber-v1.16.0-aarch64-apple-darwin.tar.gz"), + "should try amber- first: {stdout}" + ); + + let installed = install_dir.join("amber"); + assert!(installed.exists(), "expected {installed:?}"); + assert!(!install_dir.join("bee").exists()); + let probe = Command::new(&installed) + .output() + .expect("run installed amber"); + assert!(probe.status.success()); + assert_eq!(String::from_utf8_lossy(&probe.stdout).trim(), "bee-payload"); +} + +#[test] +fn install_sh_prefers_amber_archive_when_both_exist() { + let tmp = tempfile::tempdir().unwrap(); + let www = tmp.path().join("www"); + let version_dir = www.join("v1.17.0"); + fs::create_dir_all(&version_dir).unwrap(); + + let amber_stage = tmp.path().join("stage-amber"); + let bee_stage = tmp.path().join("stage-bee"); + fs::create_dir_all(&amber_stage).unwrap(); + fs::create_dir_all(&bee_stage).unwrap(); + make_tarball( + &amber_stage, + "amber", + "#!/bin/sh\necho amber-payload\n", + &version_dir.join("amber-v1.17.0-x86_64-unknown-linux-gnu.tar.gz"), + ); + make_tarball( + &bee_stage, + "bee", + "#!/bin/sh\necho bee-payload\n", + &version_dir.join("bee-v1.17.0-x86_64-unknown-linux-gnu.tar.gz"), + ); + + let server = spawn_fixture_server(&www); + let home = tmp.path().join("home"); + let install_dir = tmp.path().join("bin"); + fs::create_dir_all(&home).unwrap(); + + let output = run_install( + "v1.17.0", + "Linux", + "x86_64", + &server.base, + &install_dir, + &home, + ); + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "install.sh amber-prefer failed: {stderr}{stdout}" + ); + assert!( + !stdout.contains("bee-v1.17.0"), + "must not fall back when amber- exists: {stdout}" + ); + let probe = Command::new(install_dir.join("amber")) + .output() + .expect("run installed amber"); + assert_eq!( + String::from_utf8_lossy(&probe.stdout).trim(), + "amber-payload" + ); +} + +#[test] +fn install_sh_lists_tried_urls_when_both_assets_missing() { + let tmp = tempfile::tempdir().unwrap(); + let www = tmp.path().join("www"); + fs::create_dir_all(www.join("v1.16.0")).unwrap(); + let server = spawn_fixture_server(&www); + let home = tmp.path().join("home"); + let install_dir = tmp.path().join("bin"); + fs::create_dir_all(&home).unwrap(); + + let output = run_install( + "v1.16.0", + "Darwin", + "arm64", + &server.base, + &install_dir, + &home, + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(!output.status.success()); + assert!( + stderr.contains("download failed (tried:"), + "missing tried URLs: {stderr}" + ); + assert!(stderr.contains("amber-v1.16.0-aarch64-apple-darwin.tar.gz")); + assert!(stderr.contains("bee-v1.16.0-aarch64-apple-darwin.tar.gz")); +} + +#[test] +fn release_workflow_still_emits_amber_prefix_archives() { + let yaml = + fs::read_to_string(repo_root().join(".github/workflows/release-assets.yml")).unwrap(); + assert!(yaml.contains("amber-${RELEASE_TAG}-${TARGET}.tar.gz")); + assert!(yaml.contains("amber-${RELEASE_TAG}-${TARGET}")); + assert!( + !yaml.contains("bee-${RELEASE_TAG}") && !yaml.contains("bee-${{"), + "future releases must not go back to bee- prefixes: found bee- in workflow" + ); +} diff --git a/tests/release_workflow_tests.rs b/tests/release_workflow_tests.rs index b1e18a76..3d0928b0 100644 --- a/tests/release_workflow_tests.rs +++ b/tests/release_workflow_tests.rs @@ -435,6 +435,10 @@ fn install_sh_maps_unix_platforms_to_release_targets() { let ps1_text = fs::read_to_string(&ps1).expect("install.ps1"); assert!(ps1_text.contains("x86_64-pc-windows-msvc.zip")); assert!(ps1_text.contains("amber.exe")); + assert!( + ps1_text.contains("bee-$Version-$Target.zip") && ps1_text.contains("bee.exe"), + "install.ps1 must fall back to legacy bee- zip / bee.exe" + ); } #[test]