hni 0.0.2

ni-compatible package manager command router with node shim
Documentation
param(
  [string]$Version = "latest",
  [string]$InstallDir = "$env:LOCALAPPDATA\hni\bin"
)

$ErrorActionPreference = "Stop"
$Repo = "happytoolin/hni"
$DownloadRoot = "https://happytoolin.com"
$FallbackDownloadRoot = "https://github.com/$Repo"
$Aliases = @("ni", "nr", "nlx", "nu", "nun", "nci", "na", "np", "ns")

function Write-Log {
  param([string]$Message)
  Write-Host "[hni] $Message"
}

function Resolve-Tag {
  param([string]$RequestedVersion)

  if ($RequestedVersion -eq "latest") {
    $release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest"
    if (-not $release.tag_name) {
      throw "Unable to resolve latest release tag."
    }
    return "$($release.tag_name)"
  }

  if ($RequestedVersion.StartsWith("v")) {
    return $RequestedVersion
  }

  return "v$RequestedVersion"
}

function Resolve-Target {
  $arch = $env:PROCESSOR_ARCHITECTURE

  switch ($arch) {
    "AMD64" { return "x86_64-pc-windows-msvc" }
    "ARM64" { return "aarch64-pc-windows-msvc" }
    default { throw "Unsupported architecture: $arch" }
  }
}

function Download-Asset {
  param(
    [string]$Tag,
    [string]$Target,
    [string]$OutputPath
  )

  $asset = "hni-$Tag-$Target.zip"
  $primaryUrl = "$($DownloadRoot.TrimEnd('/'))/hni/releases/download/$Tag/$asset"
  $fallbackUrl = "$FallbackDownloadRoot/releases/download/$Tag/$asset"

  try {
    Invoke-WebRequest -Uri $primaryUrl -OutFile $OutputPath
    Write-Log "Downloaded $asset from $DownloadRoot"
  } catch {
    Write-Log "Primary URL failed, falling back to GitHub releases"
    Invoke-WebRequest -Uri $fallbackUrl -OutFile $OutputPath
  }
}

function Ensure-PathEntry {
  param([string]$PathEntry)

  $current = [Environment]::GetEnvironmentVariable("Path", "User")
  if (-not $current) {
    [Environment]::SetEnvironmentVariable("Path", $PathEntry, "User")
    return
  }

  $parts = $current.Split(';') | Where-Object { $_ -ne "" }
  if ($parts -contains $PathEntry) {
    return
  }

  [Environment]::SetEnvironmentVariable("Path", "$current;$PathEntry", "User")
}

function Write-ShellSetup {
  Write-Log "Shell setup:"
  Write-Log "  Add the hni init line at the end of your shell config, after nvm/mise/asdf/fnm/volta init."
  Write-Log '  PowerShell ($PROFILE): Invoke-Expression (& hni init powershell)'
  Write-Log '  bash (~/.bashrc): eval "$(hni init bash)"'
  Write-Log '  zsh (~/.zshrc): eval "$(hni init zsh)"'
  Write-Log '  fish (~/.config/fish/config.fish): hni init fish | source'
  Write-Log '  nushell (~/.config/nushell/config.nu):'
  Write-Log '    hni init nushell | save --force ~/.config/nushell/hni.nu'
  Write-Log '    source ~/.config/nushell/hni.nu'
}

$tag = Resolve-Tag -RequestedVersion $Version
$target = Resolve-Target

Write-Log "Installing $Repo $tag for $target"

$tmpDir = Join-Path $env:TEMP ("hni-install-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $tmpDir | Out-Null

try {
  $archive = Join-Path $tmpDir "hni.zip"
  Download-Asset -Tag $tag -Target $target -OutputPath $archive
  Expand-Archive -Path $archive -DestinationPath $tmpDir -Force

  $sourceExe = Join-Path $tmpDir "hni.exe"
  if (-not (Test-Path $sourceExe)) {
    throw "Archive does not contain hni.exe"
  }

  New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null

  $targetExe = Join-Path $InstallDir "hni.exe"
  Copy-Item -Path $sourceExe -Destination $targetExe -Force

  foreach ($alias in $Aliases) {
    Copy-Item -Path $targetExe -Destination (Join-Path $InstallDir "$alias.exe") -Force
  }

  Ensure-PathEntry -PathEntry $InstallDir
} finally {
  Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}

Write-Log "Installed to $InstallDir"
Write-ShellSetup
Write-Log "Restart your terminal to use the command immediately."