diff --git a/.github/scripts/release/r2/publish.sh b/.github/scripts/release/r2/publish.sh index 0ec38cb..5eccbb6 100755 --- a/.github/scripts/release/r2/publish.sh +++ b/.github/scripts/release/r2/publish.sh @@ -13,10 +13,10 @@ public_url="${FLAVOR_RELEASES_PUBLIC_URL%/}" version_prefix="$RELEASE_CHANNEL/versions/$RELEASE_VERSION" latest_prefix="$RELEASE_CHANNEL/latest" metadata_path="$release_root/metadata.json" -publish_root_installers=0 +publish_root_manage=0 if [ "$RELEASE_CHANNEL" = "stable" ]; then - publish_root_installers=1 + publish_root_manage=1 fi upload() { @@ -59,19 +59,9 @@ for file_path in "$release_root"/flavor-*.tar.gz "$release_root"/flavor-*.zip "$ upload "$file_path" "$version_prefix/$name" "$(artifact_content_type "$name")" "public, max-age=31536000, immutable" done -upload "$GITHUB_WORKSPACE/install.sh" "$version_prefix/install.sh" "text/x-shellscript; charset=utf-8" "public, max-age=31536000, immutable" -upload "$GITHUB_WORKSPACE/install.ps1" "$version_prefix/install.ps1" "text/plain; charset=utf-8" "public, max-age=31536000, immutable" -upload "$GITHUB_WORKSPACE/uninstall.sh" "$version_prefix/uninstall.sh" "text/x-shellscript; charset=utf-8" "public, max-age=31536000, immutable" -upload "$GITHUB_WORKSPACE/uninstall.ps1" "$version_prefix/uninstall.ps1" "text/plain; charset=utf-8" "public, max-age=31536000, immutable" -upload "$GITHUB_WORKSPACE/install.sh" "$latest_prefix/install.sh" "text/x-shellscript; charset=utf-8" "public, max-age=60, must-revalidate" -upload "$GITHUB_WORKSPACE/install.ps1" "$latest_prefix/install.ps1" "text/plain; charset=utf-8" "public, max-age=60, must-revalidate" -upload "$GITHUB_WORKSPACE/uninstall.sh" "$latest_prefix/uninstall.sh" "text/x-shellscript; charset=utf-8" "public, max-age=60, must-revalidate" -upload "$GITHUB_WORKSPACE/uninstall.ps1" "$latest_prefix/uninstall.ps1" "text/plain; charset=utf-8" "public, max-age=60, must-revalidate" -if [ "$publish_root_installers" -eq 1 ]; then - upload "$GITHUB_WORKSPACE/install.sh" "install.sh" "text/x-shellscript; charset=utf-8" "public, max-age=60, must-revalidate" - upload "$GITHUB_WORKSPACE/install.ps1" "install.ps1" "text/plain; charset=utf-8" "public, max-age=60, must-revalidate" - upload "$GITHUB_WORKSPACE/uninstall.sh" "uninstall.sh" "text/x-shellscript; charset=utf-8" "public, max-age=60, must-revalidate" - upload "$GITHUB_WORKSPACE/uninstall.ps1" "uninstall.ps1" "text/plain; charset=utf-8" "public, max-age=60, must-revalidate" +if [ "$publish_root_manage" -eq 1 ]; then + upload "$GITHUB_WORKSPACE/manage.sh" "manage.sh" "text/x-shellscript; charset=utf-8" "public, max-age=60, must-revalidate" + upload "$GITHUB_WORKSPACE/manage.ps1" "manage.ps1" "text/plain; charset=utf-8" "public, max-age=60, must-revalidate" fi PUBLIC_URL="$public_url" \ @@ -79,7 +69,7 @@ VERSION_PREFIX="$version_prefix" \ LATEST_PREFIX="$latest_prefix" \ RELEASE_ROOT="$release_root" \ METADATA_PATH="$metadata_path" \ -PUBLISH_ROOT_INSTALLERS="$publish_root_installers" \ +PUBLISH_ROOT_MANAGE="$publish_root_manage" \ python3 <<'PY' import json import os @@ -122,29 +112,9 @@ metadata = { "versionPrefix": version_prefix, "latestPrefix": latest_prefix, }, - "install": { - "unix": ( - f"{public_url}/install.sh" - if env["PUBLISH_ROOT_INSTALLERS"] == "1" - else f"{public_url}/{latest_prefix}/install.sh" - ), - "windows": ( - f"{public_url}/install.ps1" - if env["PUBLISH_ROOT_INSTALLERS"] == "1" - else f"{public_url}/{latest_prefix}/install.ps1" - ), - }, - "uninstall": { - "unix": ( - f"{public_url}/uninstall.sh" - if env["PUBLISH_ROOT_INSTALLERS"] == "1" - else f"{public_url}/{latest_prefix}/uninstall.sh" - ), - "windows": ( - f"{public_url}/uninstall.ps1" - if env["PUBLISH_ROOT_INSTALLERS"] == "1" - else f"{public_url}/{latest_prefix}/uninstall.ps1" - ), + "manage": { + "unix": f"{public_url}/manage.sh", + "windows": f"{public_url}/manage.ps1", }, "artifacts": { "linuxX64": artifact("flavor-x86_64-unknown-linux-gnu.tar.gz", "application/gzip"), diff --git a/.github/scripts/release/r2/summary.sh b/.github/scripts/release/r2/summary.sh index 5e45811..4ac9dba 100644 --- a/.github/scripts/release/r2/summary.sh +++ b/.github/scripts/release/r2/summary.sh @@ -28,12 +28,8 @@ done echo "" echo "### Links" echo "" - if [ "$RELEASE_CHANNEL" = "stable" ]; then - echo "- Stable unix installer: ${FLAVOR_RELEASES_PUBLIC_URL%/}/install.sh" - echo "- Stable windows installer: ${FLAVOR_RELEASES_PUBLIC_URL%/}/install.ps1" - echo "- Stable unix uninstaller: ${FLAVOR_RELEASES_PUBLIC_URL%/}/uninstall.sh" - echo "- Stable windows uninstaller: ${FLAVOR_RELEASES_PUBLIC_URL%/}/uninstall.ps1" - fi + echo "- Unix manager: ${FLAVOR_RELEASES_PUBLIC_URL%/}/manage.sh" + echo "- Windows manager: ${FLAVOR_RELEASES_PUBLIC_URL%/}/manage.ps1" echo "- Latest metadata: ${R2_METADATA_URL}" echo "- Version metadata: ${R2_VERSION_METADATA_URL}" } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/scripts/release/r2/verify.sh b/.github/scripts/release/r2/verify.sh index 3906f39..567ebc1 100755 --- a/.github/scripts/release/r2/verify.sh +++ b/.github/scripts/release/r2/verify.sh @@ -26,34 +26,12 @@ if metadata["channel"] != os.environ["EXPECTED_CHANNEL"]: if metadata["releaseVersion"] != os.environ["EXPECTED_RELEASE_VERSION"]: raise SystemExit(f"unexpected releaseVersion: {metadata['releaseVersion']}") expected_public_url = os.environ["EXPECTED_PUBLIC_URL"] -expected_unix = ( - f"{expected_public_url}/install.sh" - if metadata["channel"] == "stable" - else f"{expected_public_url}/{metadata['channel']}/latest/install.sh" -) -expected_windows = ( - f"{expected_public_url}/install.ps1" - if metadata["channel"] == "stable" - else f"{expected_public_url}/{metadata['channel']}/latest/install.ps1" -) -expected_uninstall_unix = ( - f"{expected_public_url}/uninstall.sh" - if metadata["channel"] == "stable" - else f"{expected_public_url}/{metadata['channel']}/latest/uninstall.sh" -) -expected_uninstall_windows = ( - f"{expected_public_url}/uninstall.ps1" - if metadata["channel"] == "stable" - else f"{expected_public_url}/{metadata['channel']}/latest/uninstall.ps1" -) -if metadata["install"]["unix"] != expected_unix: - raise SystemExit(f"unexpected unix installer url: {metadata['install']['unix']}") -if metadata["install"]["windows"] != expected_windows: - raise SystemExit(f"unexpected windows installer url: {metadata['install']['windows']}") -if metadata["uninstall"]["unix"] != expected_uninstall_unix: - raise SystemExit(f"unexpected unix uninstaller url: {metadata['uninstall']['unix']}") -if metadata["uninstall"]["windows"] != expected_uninstall_windows: - raise SystemExit(f"unexpected windows uninstaller url: {metadata['uninstall']['windows']}") +expected_manage_unix = f"{expected_public_url}/manage.sh" +expected_manage_windows = f"{expected_public_url}/manage.ps1" +if metadata["manage"]["unix"] != expected_manage_unix: + raise SystemExit(f"unexpected unix manager url: {metadata['manage']['unix']}") +if metadata["manage"]["windows"] != expected_manage_windows: + raise SystemExit(f"unexpected windows manager url: {metadata['manage']['windows']}") if metadata["channel"] == "beta": if metadata.get("betaVersion") != os.environ["EXPECTED_RELEASE_VERSION"]: raise SystemExit(f"unexpected betaVersion: {metadata.get('betaVersion')}") @@ -77,10 +55,8 @@ from pathlib import Path metadata = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) for item in metadata["artifacts"].values(): print(item["url"]) -print(metadata["install"]["unix"]) -print(metadata["install"]["windows"]) -print(metadata["uninstall"]["unix"]) -print(metadata["uninstall"]["windows"]) +print(metadata["manage"]["unix"]) +print(metadata["manage"]["windows"]) PY ); do curl -fsSI "$url" >/dev/null diff --git a/.github/scripts/release/smoke/smoke.ps1 b/.github/scripts/release/smoke/smoke.ps1 index 22fd91b..c151596 100644 --- a/.github/scripts/release/smoke/smoke.ps1 +++ b/.github/scripts/release/smoke/smoke.ps1 @@ -11,10 +11,10 @@ try { $env:FLAVOR_INSTALL_ROOT = Join-Path $tmpdir 'install' $env:FLAVOR_LOCAL_BIN_DIR = Join-Path $tmpdir 'bin' New-Item -ItemType Directory -Force -Path $env:FLAVOR_INSTALL_ROOT, $env:FLAVOR_LOCAL_BIN_DIR | Out-Null - & (Join-Path $root 'install.ps1') install --channel $channel --version $version + & (Join-Path $root 'manage.ps1') install --channel $channel --version $version --retain=false & (Join-Path $env:FLAVOR_LOCAL_BIN_DIR 'flavor.exe') --version & (Join-Path $env:FLAVOR_LOCAL_BIN_DIR 'flavor.exe') check --root $root --config (Join-Path $root 'flavor.json') - & (Join-Path $root 'uninstall.ps1') --version $version + & (Join-Path $root 'manage.ps1') uninstall --version $version if (Test-Path (Join-Path $env:FLAVOR_INSTALL_ROOT $version)) { throw "version uninstall left $(Join-Path $env:FLAVOR_INSTALL_ROOT $version)" } @@ -22,10 +22,10 @@ try { if ($env:SMOKE_LATEST -eq '1') { Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $env:FLAVOR_LOCAL_BIN_DIR 'flavor.exe') $env:FLAVOR_INSTALL_ROOT = Join-Path $tmpdir 'latest-smoke' - & (Join-Path $root 'install.ps1') install --channel $channel + & (Join-Path $root 'manage.ps1') install --channel $channel --retain=false & (Join-Path $env:FLAVOR_LOCAL_BIN_DIR 'flavor.exe') --version & (Join-Path $env:FLAVOR_LOCAL_BIN_DIR 'flavor.exe') check --root $root --config (Join-Path $root 'flavor.json') - & (Join-Path $root 'uninstall.ps1') --install-root $env:FLAVOR_INSTALL_ROOT + & (Join-Path $root 'manage.ps1') uninstall --install-root $env:FLAVOR_INSTALL_ROOT if (Test-Path $env:FLAVOR_INSTALL_ROOT) { throw "full uninstall left $env:FLAVOR_INSTALL_ROOT" } diff --git a/.github/scripts/release/smoke/smoke.sh b/.github/scripts/release/smoke/smoke.sh index ce30d2e..0f400c6 100755 --- a/.github/scripts/release/smoke/smoke.sh +++ b/.github/scripts/release/smoke/smoke.sh @@ -15,18 +15,18 @@ export FLAVOR_INSTALL_ROOT="$tmpdir/install" export FLAVOR_LOCAL_BIN_DIR="$tmpdir/bin" mkdir -p "$HOME" "$FLAVOR_INSTALL_ROOT" "$FLAVOR_LOCAL_BIN_DIR" -sh "$ROOT/install.sh" install --channel "$CHANNEL" --version "$VERSION" +sh "$ROOT/manage.sh" install --channel "$CHANNEL" --version "$VERSION" --retain=false "$FLAVOR_LOCAL_BIN_DIR/flavor" --version "$FLAVOR_LOCAL_BIN_DIR/flavor" check --root "$ROOT" --config "$ROOT/flavor.json" -sh "$ROOT/uninstall.sh" --version "$VERSION" +sh "$ROOT/manage.sh" uninstall --version "$VERSION" [ ! -e "$FLAVOR_INSTALL_ROOT/$VERSION" ] || { printf '%s\n' "version uninstall left $FLAVOR_INSTALL_ROOT/$VERSION" >&2; exit 1; } if [ "${SMOKE_LATEST:-}" = "1" ]; then rm -f "$FLAVOR_LOCAL_BIN_DIR/flavor" rm -rf "$FLAVOR_INSTALL_ROOT/latest-smoke" - sh "$ROOT/install.sh" install --channel "$CHANNEL" --install-root "$FLAVOR_INSTALL_ROOT/latest-smoke" + sh "$ROOT/manage.sh" install --channel "$CHANNEL" --install-root "$FLAVOR_INSTALL_ROOT/latest-smoke" --retain=false "$FLAVOR_LOCAL_BIN_DIR/flavor" --version "$FLAVOR_LOCAL_BIN_DIR/flavor" check --root "$ROOT" --config "$ROOT/flavor.json" - sh "$ROOT/uninstall.sh" --install-root "$FLAVOR_INSTALL_ROOT/latest-smoke" + sh "$ROOT/manage.sh" uninstall --install-root "$FLAVOR_INSTALL_ROOT/latest-smoke" [ ! -e "$FLAVOR_INSTALL_ROOT/latest-smoke" ] || { printf '%s\n' "full uninstall left $FLAVOR_INSTALL_ROOT/latest-smoke" >&2; exit 1; } fi diff --git a/AGENTS.md b/AGENTS.md index 2599c0f..cc29cf1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,9 +31,9 @@ management. - `scripts/init.py` is the idempotent post-clone initializer. It quick-fails on missing required tools or repository entrypoints, installs local hooks, and exits cleanly only when the checkout is ready for development. -- `install.sh`, `install.ps1`, `uninstall.sh`, and `uninstall.ps1` are the - public install/uninstall entrypoints at the repository root. -- Release and installer downloads use R2 metadata and artifacts as the source of +- `manage.sh` and `manage.ps1` are the public install/uninstall entrypoints at + the repository root. +- Release and manager downloads use R2 metadata and artifacts as the source of truth. ### Recursive AGENTS Index @@ -225,9 +225,8 @@ diagnostics, and typed state/config injection where needed. ### Where Do Installer Changes Go? -Public install/uninstall entrypoints live at the repository root as -`install.sh`, `install.ps1`, `uninstall.sh`, and `uninstall.ps1`. Release and -smoke scripts should reference those root files. +Public install/uninstall entrypoints live at the repository root as `manage.sh` +and `manage.ps1`. Release and smoke scripts should reference those root files. ### Where Do Workflow Helper Scripts Go? diff --git a/Cargo.lock b/Cargo.lock index 119c2e4..4063164 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flavor-cli" -version = "0.3.0" +version = "0.3.1" dependencies = [ "flavor-core", "flavor-grammar", diff --git a/README.md b/README.md index 3f08b5ec..0418e5a 100644 --- a/README.md +++ b/README.md @@ -9,43 +9,49 @@ Personal check-only code flavor lint CLI. Unix: ```bash -curl -fsSL https://flavor.perish.uk/install.sh | sh +curl -fsSL https://flavor.perish.uk/manage.sh | sh ``` Windows PowerShell: ```powershell -irm https://flavor.perish.uk/install.ps1 | pwsh +irm https://flavor.perish.uk/manage.ps1 | pwsh ``` Pin a version: ```bash -curl -fsSL https://flavor.perish.uk/install.sh | sh -s -- --version v0.1.0 +curl -fsSL https://flavor.perish.uk/manage.sh | sh -s -- install --version v0.1.0 ``` Install the latest beta: ```bash -curl -fsSL https://flavor.perish.uk/install.sh | sh -s -- --channel beta +curl -fsSL https://flavor.perish.uk/manage.sh | sh -s -- install --channel beta +``` + +Keep older installed versions instead of prompting or pruning: + +```bash +curl -fsSL https://flavor.perish.uk/manage.sh | sh -s -- install --retain=true ``` Uninstall every installed version: ```bash -curl -fsSL https://flavor.perish.uk/uninstall.sh | sh +curl -fsSL https://flavor.perish.uk/manage.sh | sh -s -- uninstall ``` Windows PowerShell: ```powershell -irm https://flavor.perish.uk/uninstall.ps1 | pwsh +& ([scriptblock]::Create((irm https://flavor.perish.uk/manage.ps1))) uninstall ``` Uninstall one version: ```bash -curl -fsSL https://flavor.perish.uk/uninstall.sh | sh -s -- --version v0.2.2 +curl -fsSL https://flavor.perish.uk/manage.sh | sh -s -- uninstall --version v0.2.2 ``` ## Usage diff --git a/crates/flavor-cli/Cargo.toml b/crates/flavor-cli/Cargo.toml index e81e46e..372ac62 100644 --- a/crates/flavor-cli/Cargo.toml +++ b/crates/flavor-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flavor-cli" -version = "0.3.0" +version = "0.3.1" edition = "2021" license = "MIT" description = "Personal check-only AST-backed code flavor lint CLI." diff --git a/install.ps1 b/manage.ps1 similarity index 55% rename from install.ps1 rename to manage.ps1 index 224f401..f71aa5a 100644 --- a/install.ps1 +++ b/manage.ps1 @@ -8,6 +8,7 @@ $version = if ($env:FLAVOR_VERSION) { $env:FLAVOR_VERSION } else { '' } $publicUrl = if ($env:FLAVOR_RELEASES_PUBLIC_URL) { $env:FLAVOR_RELEASES_PUBLIC_URL } else { 'https://releases.flavor.perish.uk' } $installRoot = if ($env:FLAVOR_INSTALL_ROOT) { $env:FLAVOR_INSTALL_ROOT } else { Join-Path $env:LOCALAPPDATA 'flavor' } $localBinDir = if ($env:FLAVOR_LOCAL_BIN_DIR) { $env:FLAVOR_LOCAL_BIN_DIR } else { Join-Path $env:USERPROFILE '.local\bin' } +$retain = if ($env:FLAVOR_RETAIN) { $env:FLAVOR_RETAIN } else { '' } for ($i = 0; $i -lt $remaining.Length; $i++) { $arg = $remaining[$i] @@ -22,15 +23,15 @@ for ($i = 0; $i -lt $remaining.Length; $i++) { '^--install-root=(.+)$' { $installRoot = $Matches[1]; continue } '^--bin-dir$' { $i++; $localBinDir = $remaining[$i]; continue } '^--bin-dir=(.+)$' { $localBinDir = $Matches[1]; continue } + '^--retain$' { $retain = 'true'; continue } + '^--retain=(.+)$' { $retain = $Matches[1]; continue } '^(-h|--help|help)$' { @' -flavor installer +flavor manager Usage: - install.ps1 - install.ps1 install [--channel stable|beta] [--version vX.Y.Z] [--public-url ] - install.ps1 upgrade [--channel stable|beta] [--version vX.Y.Z] [--public-url ] - install.ps1 uninstall + manage.ps1 install [--channel stable|beta] [--version vX.Y.Z] [--retain[=true|false]] + manage.ps1 uninstall [--version vX.Y.Z] Environment: FLAVOR_RELEASES_PUBLIC_URL # default: https://releases.flavor.perish.uk @@ -38,6 +39,7 @@ Environment: FLAVOR_VERSION FLAVOR_INSTALL_ROOT FLAVOR_LOCAL_BIN_DIR + FLAVOR_RETAIN '@ | Write-Output exit 0 } @@ -45,12 +47,49 @@ Environment: } } -function Require-PublicUrl { - return $publicUrl.TrimEnd('/') +function Normalize-Version { + param([string]$Value) + return "v$($Value.TrimStart('v'))" +} + +function Normalize-Bool { + param([string]$Value) + switch -Regex ($Value) { + '^(true|1|yes|y|on)$' { return $true } + '^(false|0|no|n|off)$' { return $false } + default { throw "invalid --retain value: $Value" } + } +} + +function Installed-Versions { + param([string]$Current) + if (![System.IO.Directory]::Exists($installRoot)) { + return @() + } + return @(Get-ChildItem -LiteralPath $installRoot -Directory | Where-Object { $_.Name -ne $Current } | ForEach-Object { $_.Name }) +} + +function Should-Retain { + param([string[]]$OldVersions) + if ($OldVersions.Length -eq 0) { + return $true + } + if (![string]::IsNullOrWhiteSpace($retain)) { + return Normalize-Bool $retain + } + if ([Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { + $answer = Read-Host 'flavor: remove previously installed versions after install? [y/N]' + if ($answer -match '^(y|yes)$') { + return $false + } + return $true + } + [Console]::Error.WriteLine('flavor: preserving previous versions; pass --retain=false to prune after install') + return $true } function Install-Flavor { - $resolvedPublicUrl = Require-PublicUrl + $resolvedPublicUrl = $publicUrl.TrimEnd('/') $resolvedVersion = $version if ([string]::IsNullOrWhiteSpace($resolvedVersion)) { $metadataUrl = "$resolvedPublicUrl/$channel/latest/metadata.json" @@ -60,6 +99,9 @@ function Install-Flavor { throw 'failed to resolve latest flavor version' } } + $resolvedVersion = Normalize-Version $resolvedVersion + $oldVersions = Installed-Versions $resolvedVersion + $retainOld = Should-Retain $oldVersions $archive = 'flavor-x86_64-pc-windows-msvc.zip' $tmpdir = Join-Path ([System.IO.Path]::GetTempPath()) ("flavor-" + [System.Guid]::NewGuid().ToString('N')) @@ -68,11 +110,20 @@ function Install-Flavor { $archivePath = Join-Path $tmpdir $archive Invoke-WebRequest -Uri "$resolvedPublicUrl/$channel/versions/$resolvedVersion/$archive" -OutFile $archivePath $versionRoot = Join-Path $installRoot $resolvedVersion + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $versionRoot New-Item -ItemType Directory -Force -Path $versionRoot | Out-Null Expand-Archive -LiteralPath $archivePath -DestinationPath $versionRoot -Force New-Item -ItemType Directory -Force -Path $localBinDir | Out-Null Copy-Item -Force (Join-Path $versionRoot 'flavor.exe') (Join-Path $localBinDir 'flavor.exe') & (Join-Path $localBinDir 'flavor.exe') --version + + if (!$retainOld) { + foreach ($oldVersion in $oldVersions) { + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $oldVersion) + Write-Output "removed old flavor $oldVersion from $installRoot" + } + } + Write-Output "installed flavor to $(Join-Path $localBinDir 'flavor.exe')" } finally { @@ -80,41 +131,53 @@ function Install-Flavor { } } +function Remove-EmptyDir { + param([string]$Path) + if ([System.IO.Directory]::Exists($Path)) { + try { + Remove-Item -Force -ErrorAction Stop $Path + } + catch [System.IO.IOException] {} + } +} + +function Installed-Version { + $binPath = Join-Path $localBinDir 'flavor.exe' + if (![System.IO.File]::Exists($binPath)) { + return '' + } + try { + $output = & $binPath --version + if ($output -match 'v?([0-9]+\.[0-9]+\.[0-9]+(?:[-.][A-Za-z0-9]+)*)') { + return "v$($Matches[1].TrimStart('v'))" + } + } + catch {} + return '' +} + function Uninstall-Flavor { $binPath = Join-Path $localBinDir 'flavor.exe' if (![string]::IsNullOrWhiteSpace($version)) { - $normalizedVersion = "v$($version.TrimStart('v'))" - if ([System.IO.File]::Exists($binPath)) { - try { - $output = & $binPath --version - if ($output -match 'v?([0-9]+\.[0-9]+\.[0-9]+(?:[-.][A-Za-z0-9]+)*)') { - $installedVersion = "v$($Matches[1].TrimStart('v'))" - if ($installedVersion -eq $normalizedVersion) { - Remove-Item -Force -ErrorAction SilentlyContinue $binPath - Write-Output "removed $binPath" - } - } - } - catch {} - } - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $version) - if ($version -ne $normalizedVersion) { - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $normalizedVersion) + $normalizedVersion = Normalize-Version $version + if ((Installed-Version) -eq $normalizedVersion) { + Remove-Item -Force -ErrorAction SilentlyContinue $binPath + Write-Output "removed $binPath" } - try { Remove-Item -Force -ErrorAction Stop $installRoot } catch [System.IO.IOException] {} - Write-Output "removed flavor $version from $installRoot" + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $normalizedVersion) + Remove-EmptyDir $installRoot + Write-Output "removed flavor $normalizedVersion from $installRoot" return } Remove-Item -Force -ErrorAction SilentlyContinue $binPath Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $installRoot - try { Remove-Item -Force -ErrorAction Stop $localBinDir } catch [System.IO.IOException] {} + Remove-EmptyDir $localBinDir Write-Output "removed flavor from $installRoot and $binPath" } switch ($command) { 'install' { Install-Flavor } - 'upgrade' { Install-Flavor } 'uninstall' { Uninstall-Flavor } default { throw "unknown command: $command" } } diff --git a/install.sh b/manage.sh similarity index 65% rename from install.sh rename to manage.sh index f4ff0f5..cce1506 100755 --- a/install.sh +++ b/manage.sh @@ -9,6 +9,7 @@ VERSION=${FLAVOR_VERSION:-} PUBLIC_URL=${FLAVOR_RELEASES_PUBLIC_URL:-https://releases.flavor.perish.uk} INSTALL_ROOT=${FLAVOR_INSTALL_ROOT:-"$HOME/.local/share/flavor"} LOCAL_BIN_DIR=${FLAVOR_LOCAL_BIN_DIR:-"$HOME/.local/bin"} +RETAIN=${FLAVOR_RETAIN:-} while [ $# -gt 0 ]; do case "$1" in @@ -57,15 +58,21 @@ while [ $# -gt 0 ]; do LOCAL_BIN_DIR=${1#--bin-dir=} shift ;; + --retain) + RETAIN=true + shift + ;; + --retain=*) + RETAIN=${1#--retain=} + shift + ;; -h|--help|help) cat <<'EOF' -flavor installer +flavor manager Usage: - install.sh - install.sh install [--channel stable|beta] [--version vX.Y.Z] [--public-url ] - install.sh upgrade [--channel stable|beta] [--version vX.Y.Z] [--public-url ] - install.sh uninstall + manage.sh install [--channel stable|beta] [--version vX.Y.Z] [--retain[=true|false]] + manage.sh uninstall [--version vX.Y.Z] Environment: FLAVOR_RELEASES_PUBLIC_URL # default: https://releases.flavor.perish.uk @@ -73,6 +80,7 @@ Environment: FLAVOR_VERSION FLAVOR_INSTALL_ROOT FLAVOR_LOCAL_BIN_DIR + FLAVOR_RETAIN EOF exit 0 ;; @@ -87,6 +95,18 @@ need_public_url() { PUBLIC_URL=${PUBLIC_URL%/} } +normalize_bool() { + case "$1" in + true|1|yes|y|on) printf '%s' true ;; + false|0|no|n|off) printf '%s' false ;; + *) echo "invalid --retain value: $1" >&2; exit 1 ;; + esac +} + +normalize_version() { + printf 'v%s' "$(printf '%s' "$1" | sed 's/^v//')" +} + platform_archive() { os=$(uname -s) arch=$(uname -m) @@ -103,6 +123,40 @@ latest_version() { sed -n 's/.*"releaseVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata" | head -n 1 } +old_versions() { + current="$1" + [ -d "$INSTALL_ROOT" ] || return 0 + for path in "$INSTALL_ROOT"/*; do + [ -d "$path" ] || continue + name=$(basename "$path") + [ "$name" != "$current" ] || continue + printf '%s\n' "$name" + done +} + +retain_old_versions() { + old="$1" + if [ -z "$old" ]; then + printf '%s' true + return + fi + if [ -n "$RETAIN" ]; then + normalize_bool "$RETAIN" + return + fi + if [ -t 0 ]; then + printf 'flavor: remove previously installed versions after install? [y/N] ' >&2 + IFS= read -r answer || answer= + case "$answer" in + y|Y|yes|YES|Yes) printf '%s' false ;; + *) printf '%s' true ;; + esac + return + fi + echo "flavor: preserving previous versions; pass --retain=false to prune after install" >&2 + printf '%s' true +} + install_flavor() { need_public_url tmpdir=$(mktemp -d) @@ -113,11 +167,16 @@ install_flavor() { VERSION=$(latest_version "$tmpdir/metadata.json") [ -n "$VERSION" ] || { echo "failed to resolve latest flavor version" >&2; exit 1; } fi + VERSION=$(normalize_version "$VERSION") + + old=$(old_versions "$VERSION") + retain=$(retain_old_versions "$old") archive=$(platform_archive) archive_url="$PUBLIC_URL/$CHANNEL/versions/$VERSION/$archive" - mkdir -p "$INSTALL_ROOT/$VERSION" "$LOCAL_BIN_DIR" curl -fsSL "$archive_url" -o "$tmpdir/$archive" + rm -rf "$INSTALL_ROOT/$VERSION" + mkdir -p "$INSTALL_ROOT/$VERSION" "$LOCAL_BIN_DIR" tar -xzf "$tmpdir/$archive" -C "$INSTALL_ROOT/$VERSION" chmod +x "$INSTALL_ROOT/$VERSION/flavor" @@ -125,39 +184,51 @@ install_flavor() { rm -f "$link" ln -s "$INSTALL_ROOT/$VERSION/flavor" "$link" "$link" --version + + if [ "$retain" = false ]; then + printf '%s\n' "$old" | while IFS= read -r old_version; do + [ -n "$old_version" ] || continue + rm -rf "$INSTALL_ROOT/$old_version" + printf 'removed old flavor %s from %s\n' "$old_version" "$INSTALL_ROOT" + done + fi + printf 'installed flavor to %s\n' "$link" } +remove_empty_dir() { + dir="$1" + if [ -d "$dir" ]; then + rmdir "$dir" 2>/dev/null || true + fi +} + uninstall_flavor() { bin_path="$LOCAL_BIN_DIR/flavor" if [ -n "$VERSION" ]; then - normalized_version="v$(printf '%s' "$VERSION" | sed 's/^v//')" + VERSION=$(normalize_version "$VERSION") target="$INSTALL_ROOT/$VERSION/flavor" - normalized_target="$INSTALL_ROOT/$normalized_version/flavor" if [ -L "$bin_path" ]; then link_target=$(readlink "$bin_path" || true) - if [ "$link_target" = "$target" ] || [ "$link_target" = "$normalized_target" ]; then + if [ "$link_target" = "$target" ]; then rm -f "$bin_path" printf 'removed %s\n' "$bin_path" fi fi rm -rf "$INSTALL_ROOT/$VERSION" - if [ "$normalized_version" != "$VERSION" ]; then - rm -rf "$INSTALL_ROOT/$normalized_version" - fi - rmdir "$INSTALL_ROOT" 2>/dev/null || true + remove_empty_dir "$INSTALL_ROOT" printf 'removed flavor %s from %s\n' "$VERSION" "$INSTALL_ROOT" return fi rm -f "$bin_path" rm -rf "$INSTALL_ROOT" - rmdir "$LOCAL_BIN_DIR" 2>/dev/null || true + remove_empty_dir "$LOCAL_BIN_DIR" printf 'removed flavor from %s and %s\n' "$INSTALL_ROOT" "$bin_path" } case "$COMMAND" in - install|upgrade) install_flavor ;; + install) install_flavor ;; uninstall) uninstall_flavor ;; *) echo "unknown command: $COMMAND" >&2 diff --git a/scripts/cli/cloudflare.py b/scripts/cli/cloudflare.py index 6e508c2..c7682c3 100644 --- a/scripts/cli/cloudflare.py +++ b/scripts/cli/cloudflare.py @@ -4,7 +4,7 @@ import json from lib.cloudflare import ( - INSTALL_RULE_SPECS, + MANAGE_RULE_SPECS, TOKEN_FILE, add_rule, api_request, @@ -12,7 +12,7 @@ ensure_local_layout, find_phase_ruleset, get_ruleset, - install_rule_definition, + manage_rule_definition, load_config, masked, resolve_zone_id, @@ -29,9 +29,9 @@ def usage() -> None: Commands: init create repo-local .local/secrets/cloudflare.env template check validate repo-local credentials and probe core account APIs - install-plan print the desired install/uninstall redirect rule shape - install-inspect inspect current dynamic redirect ruleset for install/uninstall rules - install-ensure-redirect create/update exact-path install/uninstall redirects (use --dry-run first) + manage-plan print the desired manage redirect rule shape + manage-inspect inspect current dynamic redirect ruleset for manage rules + manage-ensure-redirect create/update exact-path manage redirects (use --dry-run first) api authenticated Cloudflare API call using repo-local token [--query key=value]... optional query params [--json ] optional JSON body @@ -85,7 +85,7 @@ def cmd_check(args: list[str]) -> int: print(f"account name: {account['result']['name']}") elif account_error: print("account probe: skipped (token is not authorized for account details)") - print(f"install zone: {config.zone_name} ({zone_id})") + print(f"manage zone: {config.zone_name} ({zone_id})") print(f"zone rulesets: {len(rulesets.get('result', []))}") print("zones:") for zone in zones.get("result", []): @@ -99,59 +99,59 @@ def cmd_check(args: list[str]) -> int: return 0 -def cmd_install_plan(args: list[str]) -> int: +def cmd_manage_plan(args: list[str]) -> int: if args: - raise RuntimeError("install-plan does not accept arguments") + raise RuntimeError("manage-plan does not accept arguments") config = load_config() - print("install redirect plan") + print("manage redirect plan") print(f"zone: {config.zone_name}") - print(f"request host: {config.install_host}") - print(f"redirect host: {config.install_origin_host}") + print(f"request host: {config.manage_host}") + print(f"redirect host: {config.manage_origin_host}") print("phase: http_request_dynamic_redirect") print("rules:") - for spec in INSTALL_RULE_SPECS: - rule = install_rule_definition(config, spec) + for spec in MANAGE_RULE_SPECS: + rule = manage_rule_definition(config, spec) print(json.dumps(rule, indent=2, sort_keys=True)) return 0 -def cmd_install_inspect(args: list[str]) -> int: +def cmd_manage_inspect(args: list[str]) -> int: if args: - raise RuntimeError("install-inspect does not accept arguments") + raise RuntimeError("manage-inspect does not accept arguments") config = load_config() zone_id = resolve_zone_id(config) ruleset = find_phase_ruleset(config, zone_id, phase="http_request_dynamic_redirect") if ruleset is None: - print("install inspect: no http_request_dynamic_redirect zone ruleset found") + print("manage inspect: no http_request_dynamic_redirect zone ruleset found") return 0 ruleset = get_ruleset(config, zone_id, ruleset["id"]) print(f"zone id: {zone_id}") print(f"ruleset id: {ruleset['id']}") print(f"ruleset name: {ruleset['name']}") - matched = [rule for rule in ruleset.get("rules", []) if rule.get("ref") in {spec.ref for spec in INSTALL_RULE_SPECS}] + matched = [rule for rule in ruleset.get("rules", []) if rule.get("ref") in {spec.ref for spec in MANAGE_RULE_SPECS}] if not matched: - print("install inspect: no install/uninstall redirect rules found") + print("manage inspect: no manage redirect rules found") return 0 - print("install rules:") + print("manage rules:") print(json.dumps(matched, indent=2, sort_keys=True)) return 0 -def cmd_install_ensure_redirect(args: list[str]) -> int: - parser = argparse.ArgumentParser(prog="./cli.sh :cloudflare install-ensure-redirect", add_help=False) +def cmd_manage_ensure_redirect(args: list[str]) -> int: + parser = argparse.ArgumentParser(prog="./cli.sh :cloudflare manage-ensure-redirect", add_help=False) parser.add_argument("--dry-run", action="store_true") parsed = parser.parse_args(args) config = load_config() zone_id = resolve_zone_id(config) - planned_rules = [install_rule_definition(config, spec) for spec in INSTALL_RULE_SPECS] + planned_rules = [manage_rule_definition(config, spec) for spec in MANAGE_RULE_SPECS] if parsed.dry_run: payload = { "zone": config.zone_name, "zone_id": zone_id, - "request_host": config.install_host, - "redirect_host": config.install_origin_host, + "request_host": config.manage_host, + "redirect_host": config.manage_origin_host, "phase": "http_request_dynamic_redirect", "planned_rules": planned_rules, } @@ -180,7 +180,7 @@ def cmd_install_ensure_redirect(args: list[str]) -> int: update_rule(config, zone_id, ruleset["id"], current["id"], planned_rule) changed.append(f"updated {planned_rule['ref']}") - print("install ensure redirect: ok") + print("manage ensure redirect: ok") for item in changed: print(f" - {item}") return 0 @@ -223,9 +223,9 @@ def cmd_api(args: list[str]) -> int: COMMANDS = { "init": cmd_init, "check": cmd_check, - "install-plan": cmd_install_plan, - "install-inspect": cmd_install_inspect, - "install-ensure-redirect": cmd_install_ensure_redirect, + "manage-plan": cmd_manage_plan, + "manage-inspect": cmd_manage_inspect, + "manage-ensure-redirect": cmd_manage_ensure_redirect, "api": cmd_api, } diff --git a/scripts/init.py b/scripts/init.py index 84d1563..89de969 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -33,10 +33,8 @@ "Cargo.lock", "cli.sh", "flavor.json", - "install.sh", - "install.ps1", - "uninstall.sh", - "uninstall.ps1", + "manage.sh", + "manage.ps1", ".github/workflows/guard.yml", ".github/scripts/release/r2/publish.sh", ".github/scripts/release/smoke/smoke.sh", @@ -67,8 +65,7 @@ echo "==> shell syntax" sh -n cli.sh -sh -n install.sh -sh -n uninstall.sh +sh -n manage.sh bash -n .github/scripts/release/r2/publish.sh sh -n .github/scripts/release/smoke/smoke.sh @@ -91,7 +88,7 @@ [scriptblock]::Create((Get-Content -Raw $path)) | Out-Null }} ' \\ - install.ps1 uninstall.ps1 .github/scripts/release/smoke/smoke.ps1 + manage.ps1 .github/scripts/release/smoke/smoke.ps1 else echo "==> PowerShell syntax" echo "skip: pwsh not found" diff --git a/scripts/lib/cloudflare.py b/scripts/lib/cloudflare.py index 07ff6bf..0dfa351 100644 --- a/scripts/lib/cloudflare.py +++ b/scripts/lib/cloudflare.py @@ -17,10 +17,10 @@ REQUIRED_KEYS = ("CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN") API_BASE = "https://api.cloudflare.com/client/v4" DEFAULT_ZONE_NAME = "perish.uk" -DEFAULT_INSTALL_HOST = "flavor.perish.uk" -DEFAULT_INSTALL_ORIGIN_HOST = "releases.flavor.perish.uk" -DEFAULT_INSTALL_REDIRECT_PREFIX = "stable/latest" -INSTALL_REDIRECT_STATUS_CODE = 302 +DEFAULT_MANAGE_HOST = "flavor.perish.uk" +DEFAULT_MANAGE_ORIGIN_HOST = "releases.flavor.perish.uk" +DEFAULT_MANAGE_REDIRECT_PREFIX = "" +MANAGE_REDIRECT_STATUS_CODE = 302 @dataclass(frozen=True) @@ -28,38 +28,28 @@ class CloudflareConfig: account_id: str api_token: str zone_name: str - install_host: str - install_origin_host: str - install_redirect_prefix: str + manage_host: str + manage_origin_host: str + manage_redirect_prefix: str @dataclass(frozen=True) -class InstallRuleSpec: +class ManageRuleSpec: ref: str description: str path: str -INSTALL_RULE_SPECS = ( - InstallRuleSpec( - ref="flavor_install_sh_redirect", - description="Redirect flavor install.sh to releases bucket asset", - path="/install.sh", +MANAGE_RULE_SPECS = ( + ManageRuleSpec( + ref="flavor_manage_sh_redirect", + description="Redirect flavor manage.sh to releases bucket asset", + path="/manage.sh", ), - InstallRuleSpec( - ref="flavor_install_ps1_redirect", - description="Redirect flavor install.ps1 to releases bucket asset", - path="/install.ps1", - ), - InstallRuleSpec( - ref="flavor_uninstall_sh_redirect", - description="Redirect flavor uninstall.sh to releases bucket asset", - path="/uninstall.sh", - ), - InstallRuleSpec( - ref="flavor_uninstall_ps1_redirect", - description="Redirect flavor uninstall.ps1 to releases bucket asset", - path="/uninstall.ps1", + ManageRuleSpec( + ref="flavor_manage_ps1_redirect", + description="Redirect flavor manage.ps1 to releases bucket asset", + path="/manage.ps1", ), ) @@ -94,11 +84,11 @@ def load_config() -> CloudflareConfig: account_id=values["CLOUDFLARE_ACCOUNT_ID"], api_token=values["CLOUDFLARE_API_TOKEN"], zone_name=values.get("CLOUDFLARE_ZONE_NAME", DEFAULT_ZONE_NAME), - install_host=values.get("CLOUDFLARE_INSTALL_HOST", DEFAULT_INSTALL_HOST), - install_origin_host=values.get("CLOUDFLARE_INSTALL_ORIGIN_HOST", DEFAULT_INSTALL_ORIGIN_HOST), - install_redirect_prefix=values.get( - "CLOUDFLARE_INSTALL_REDIRECT_PREFIX", - DEFAULT_INSTALL_REDIRECT_PREFIX, + manage_host=values.get("CLOUDFLARE_MANAGE_HOST", DEFAULT_MANAGE_HOST), + manage_origin_host=values.get("CLOUDFLARE_MANAGE_ORIGIN_HOST", DEFAULT_MANAGE_ORIGIN_HOST), + manage_redirect_prefix=values.get( + "CLOUDFLARE_MANAGE_REDIRECT_PREFIX", + DEFAULT_MANAGE_REDIRECT_PREFIX, ).strip("/"), ) @@ -115,9 +105,9 @@ def write_template() -> bool: "CLOUDFLARE_ACCOUNT_ID=", "CLOUDFLARE_API_TOKEN=", f"CLOUDFLARE_ZONE_NAME={DEFAULT_ZONE_NAME}", - f"CLOUDFLARE_INSTALL_HOST={DEFAULT_INSTALL_HOST}", - f"CLOUDFLARE_INSTALL_ORIGIN_HOST={DEFAULT_INSTALL_ORIGIN_HOST}", - f"CLOUDFLARE_INSTALL_REDIRECT_PREFIX={DEFAULT_INSTALL_REDIRECT_PREFIX}", + f"CLOUDFLARE_MANAGE_HOST={DEFAULT_MANAGE_HOST}", + f"CLOUDFLARE_MANAGE_ORIGIN_HOST={DEFAULT_MANAGE_ORIGIN_HOST}", + f"CLOUDFLARE_MANAGE_REDIRECT_PREFIX={DEFAULT_MANAGE_REDIRECT_PREFIX}", "", ] ), @@ -170,15 +160,15 @@ def api_request( return payload -def install_redirect_url(config: CloudflareConfig, spec: InstallRuleSpec) -> str: - if not config.install_redirect_prefix: - return f"https://{config.install_origin_host}{spec.path}" - return f"https://{config.install_origin_host}/{config.install_redirect_prefix}{spec.path}" +def manage_redirect_url(config: CloudflareConfig, spec: ManageRuleSpec) -> str: + if not config.manage_redirect_prefix: + return f"https://{config.manage_origin_host}{spec.path}" + return f"https://{config.manage_origin_host}/{config.manage_redirect_prefix}{spec.path}" -def install_rule_definition(config: CloudflareConfig, spec: InstallRuleSpec) -> dict[str, Any]: +def manage_rule_definition(config: CloudflareConfig, spec: ManageRuleSpec) -> dict[str, Any]: expression = ( - f'(http.host eq "{config.install_host}" and http.request.uri.path eq "{spec.path}")' + f'(http.host eq "{config.manage_host}" and http.request.uri.path eq "{spec.path}")' ) return { "ref": spec.ref, @@ -189,9 +179,9 @@ def install_rule_definition(config: CloudflareConfig, spec: InstallRuleSpec) -> "action_parameters": { "from_value": { "target_url": { - "value": install_redirect_url(config, spec), + "value": manage_redirect_url(config, spec), }, - "status_code": INSTALL_REDIRECT_STATUS_CODE, + "status_code": MANAGE_REDIRECT_STATUS_CODE, "preserve_query_string": False, }, }, diff --git a/uninstall.ps1 b/uninstall.ps1 deleted file mode 100644 index 78b6a38..0000000 --- a/uninstall.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -$ErrorActionPreference = 'Stop' - -$version = if ($env:FLAVOR_VERSION) { $env:FLAVOR_VERSION } else { '' } -$installRoot = if ($env:FLAVOR_INSTALL_ROOT) { $env:FLAVOR_INSTALL_ROOT } else { Join-Path $env:LOCALAPPDATA 'flavor' } -$localBinDir = if ($env:FLAVOR_LOCAL_BIN_DIR) { $env:FLAVOR_LOCAL_BIN_DIR } else { Join-Path $env:USERPROFILE '.local\bin' } - -for ($i = 0; $i -lt $args.Length; $i++) { - $arg = $args[$i] - switch -Regex ($arg) { - '^--version$' { $i++; $version = $args[$i]; continue } - '^--version=(.+)$' { $version = $Matches[1]; continue } - '^--install-root$' { $i++; $installRoot = $args[$i]; continue } - '^--install-root=(.+)$' { $installRoot = $Matches[1]; continue } - '^--bin-dir$' { $i++; $localBinDir = $args[$i]; continue } - '^--bin-dir=(.+)$' { $localBinDir = $Matches[1]; continue } - '^(-h|--help|help)$' { - @' -flavor uninstaller - -Usage: - uninstall.ps1 - uninstall.ps1 --version vX.Y.Z - -Environment: - FLAVOR_VERSION - FLAVOR_INSTALL_ROOT - FLAVOR_LOCAL_BIN_DIR -'@ | Write-Output - exit 0 - } - default { throw "unknown argument: $arg" } - } -} - -$binPath = Join-Path $localBinDir 'flavor.exe' - -function Remove-EmptyDir { - param([string]$Path) - if ([System.IO.Directory]::Exists($Path)) { - try { - Remove-Item -Force -ErrorAction Stop $Path - } - catch [System.IO.IOException] {} - } -} - -function Installed-Version { - if (![System.IO.File]::Exists($binPath)) { - return '' - } - try { - $output = & $binPath --version - if ($output -match 'v?([0-9]+\.[0-9]+\.[0-9]+(?:[-.][A-Za-z0-9]+)*)') { - return "v$($Matches[1].TrimStart('v'))" - } - } - catch {} - return '' -} - -if (![string]::IsNullOrWhiteSpace($version)) { - $normalizedVersion = "v$($version.TrimStart('v'))" - if ((Installed-Version) -eq $normalizedVersion) { - Remove-Item -Force -ErrorAction SilentlyContinue $binPath - Write-Output "removed $binPath" - } - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $version) - if ($version -ne $normalizedVersion) { - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $installRoot $normalizedVersion) - } - Remove-EmptyDir $installRoot - Write-Output "removed flavor $version from $installRoot" - exit 0 -} - -Remove-Item -Force -ErrorAction SilentlyContinue $binPath -Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $installRoot -Remove-EmptyDir $localBinDir -Write-Output "removed flavor from $installRoot and $binPath" diff --git a/uninstall.sh b/uninstall.sh deleted file mode 100755 index 9b79015..0000000 --- a/uninstall.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env sh -set -eu - -VERSION=${FLAVOR_VERSION:-} -INSTALL_ROOT=${FLAVOR_INSTALL_ROOT:-"$HOME/.local/share/flavor"} -LOCAL_BIN_DIR=${FLAVOR_LOCAL_BIN_DIR:-"$HOME/.local/bin"} - -while [ $# -gt 0 ]; do - case "$1" in - --version) - VERSION=${2:-} - [ -n "$VERSION" ] || { echo "--version requires a value" >&2; exit 1; } - shift 2 - ;; - --version=*) - VERSION=${1#--version=} - shift - ;; - --install-root) - INSTALL_ROOT=${2:-} - [ -n "$INSTALL_ROOT" ] || { echo "--install-root requires a value" >&2; exit 1; } - shift 2 - ;; - --install-root=*) - INSTALL_ROOT=${1#--install-root=} - shift - ;; - --bin-dir) - LOCAL_BIN_DIR=${2:-} - [ -n "$LOCAL_BIN_DIR" ] || { echo "--bin-dir requires a value" >&2; exit 1; } - shift 2 - ;; - --bin-dir=*) - LOCAL_BIN_DIR=${1#--bin-dir=} - shift - ;; - -h|--help|help) - cat <<'EOF' -flavor uninstaller - -Usage: - uninstall.sh - uninstall.sh --version vX.Y.Z - -Environment: - FLAVOR_VERSION - FLAVOR_INSTALL_ROOT - FLAVOR_LOCAL_BIN_DIR -EOF - exit 0 - ;; - *) - echo "unknown argument: $1" >&2 - exit 1 - ;; - esac -done - -bin_path="$LOCAL_BIN_DIR/flavor" - -remove_bin_if_current_version() { - version="$1" - target="$INSTALL_ROOT/$version/flavor" - if [ -L "$bin_path" ]; then - link_target=$(readlink "$bin_path" || true) - if [ "$link_target" = "$target" ]; then - rm -f "$bin_path" - printf 'removed %s\n' "$bin_path" - fi - fi -} - -remove_empty_dir() { - dir="$1" - if [ -d "$dir" ]; then - rmdir "$dir" 2>/dev/null || true - fi -} - -if [ -n "$VERSION" ]; then - normalized_version="v$(printf '%s' "$VERSION" | sed 's/^v//')" - remove_bin_if_current_version "$VERSION" - if [ "$normalized_version" != "$VERSION" ]; then - remove_bin_if_current_version "$normalized_version" - fi - rm -rf "$INSTALL_ROOT/$VERSION" - if [ "$normalized_version" != "$VERSION" ]; then - rm -rf "$INSTALL_ROOT/$normalized_version" - fi - remove_empty_dir "$INSTALL_ROOT" - printf 'removed flavor %s from %s\n' "$VERSION" "$INSTALL_ROOT" - exit 0 -fi - -rm -f "$bin_path" -rm -rf "$INSTALL_ROOT" -remove_empty_dir "$LOCAL_BIN_DIR" -printf 'removed flavor from %s and %s\n' "$INSTALL_ROOT" "$bin_path"