From c34206ddc3a2ab12abb0394aafdbb9e569c85f9b Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Wed, 29 Jul 2026 22:13:02 +0100 Subject: [PATCH 1/2] feat: add install.ps1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors install.sh: pinned version bumped by release-please, sha256 from checksums.txt, ~\.local\bin, PATH edited unless told otherwise. Two Windows-specific wrinkles. PATH goes through the HKCU\Environment registry key as ExpandString, because [Environment]::SetEnvironmentVariable rewrites REG_EXPAND_SZ as REG_SZ and would break any %VAR% already in PATH; a dummy User-scope write then broadcasts WM_SETTINGCHANGE so open shells reread it. And `iex` cannot pass arguments, so the parameters are backed by the same FLAGSMITH_* environment variables and the docs show the scriptblock form. Add-CiPath appends the install dir to $GITHUB_PATH, so a GitHub Actions step after the install finds the CLI on PATH — which is what the release smoke job relies on. beep boop --- .github/workflows/pull-request.yml | 17 +++ .github/workflows/release.yml | 13 +++ README.md | 6 ++ install.ps1 | 168 +++++++++++++++++++++++++++++ release-please-config.json | 3 +- 5 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 install.ps1 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index bd66a95..cad9718 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -44,6 +44,23 @@ jobs: version: v2.11.4 # keep in step with the hook rev in .pre-commit-config.yaml - run: go mod tidy -diff + install-ps1: + name: install.ps1 lint + runs-on: windows-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: PSScriptAnalyzer + shell: pwsh + run: | + Install-Module PSScriptAnalyzer -Force -Scope CurrentUser -SkipPublisherCheck + $found = Invoke-ScriptAnalyzer -Path ./install.ps1 -Severity Warning, Error + $found | Format-Table -AutoSize + if ($found) { exit 1 } + - run: ./install.ps1 -DryRun + shell: pwsh + cross-compile: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cd2094d..04a9cb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,3 +64,16 @@ jobs: persist-credentials: false - run: sh install.sh --version "$GITHUB_REF_NAME" --bin-dir "$RUNNER_TEMP/bin" - run: flagsmith --version + + install-script-windows: + name: install.ps1 + needs: goreleaser + runs-on: windows-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: ./install.ps1 -Version $env:GITHUB_REF_NAME + shell: pwsh + - run: flagsmith --version + shell: pwsh diff --git a/README.md b/README.md index 2879bbc..f874cba 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ To pin the installer itself, fetch it at a commit you trust: `raw.githubusercont Alternatively, `go install github.com/Flagsmith/flagsmith-cli@latest`, or grab an archive from [Releases](https://github.com/Flagsmith/flagsmith-cli/releases). +On Windows: + +```powershell +irm https://raw.githubusercontent.com/Flagsmith/flagsmith-cli/main/install.ps1 | iex +``` + ## Build ```sh diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..2e2204e --- /dev/null +++ b/install.ps1 @@ -0,0 +1,168 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Install the Flagsmith CLI. +.DESCRIPTION + irm https://get.flagsmith.com/install.ps1 | iex + + `iex` cannot pass arguments, so either set the environment variables below + first, or invoke a script block: + + &([scriptblock]::Create((irm https://get.flagsmith.com/install.ps1))) -Version +.PARAMETER Version + Version to install. Defaults to $env:FLAGSMITH_CLI_VERSION, else the version + this script shipped with. +.PARAMETER BinDir + Where to install. Defaults to $env:FLAGSMITH_INSTALL_DIR, else ~\.local\bin. +.PARAMETER NoModifyPath + Leave the user PATH alone. Also $env:FLAGSMITH_NO_MODIFY_PATH. +.PARAMETER DryRun + Report what would be installed, then stop. +#> +param( + [string]$Version, + [string]$BinDir, + [switch]$NoModifyPath, + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' +# Invoke-WebRequest spends most of its time drawing the progress bar. +$ProgressPreference = 'SilentlyContinue' + +$DefaultVersion = 'v2.0.0-beta.1' # x-release-please-version + +$Repo = 'Flagsmith/flagsmith-cli' +$ExeName = 'flagsmith.exe' +$BaseUrl = if ($env:FLAGSMITH_CLI_BASE_URL) { + $env:FLAGSMITH_CLI_BASE_URL +} else { + "https://github.com/$Repo/releases/download" +} + +function Get-TargetArch { + $arch = try { + [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() + } catch { + # PROCESSOR_ARCHITECTURE from the registry, not the environment: a 32-bit + # PowerShell under WOW64 reports x86 for its own process. + (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE + } + switch -Regex ($arch) { + '^(X64|AMD64)$' { return 'amd64' } + '^ARM64$' { return 'arm64' } + default { throw "unsupported architecture '$arch'" } + } +} + +function Get-Checksum { + param([string]$SumsFile, [string]$Name) + + $pattern = '\s\*?' + [regex]::Escape($Name) + '$' + $lines = @(Get-Content -LiteralPath $SumsFile | Where-Object { $_ -match $pattern }) + if ($lines.Count -ne 1) { + throw "expected exactly one checksum for $Name in checksums.txt, found $($lines.Count)" + } + return ($lines[0] -split '\s+')[0] +} + +# Add-UserPath adds to the user PATH through the registry rather than +# [Environment]::SetEnvironmentVariable, which rewrites REG_EXPAND_SZ as REG_SZ +# and so breaks any %VAR% already in PATH. +function Add-UserPath { + param([string]$Dir) + + $key = 'registry::HKEY_CURRENT_USER\Environment' + $current = (Get-Item -LiteralPath $key).GetValue('Path', '', 'DoNotExpandEnvironmentNames') -split ';' -ne '' + if ($Dir -in $current) { return $false } + + Set-ItemProperty -LiteralPath $key -Name Path -Type ExpandString -Value ((, $Dir + $current) -join ';') + # Tell running shells and Explorer to reread the environment. + $dummy = 'flagsmith-' + [guid]::NewGuid().ToString() + [Environment]::SetEnvironmentVariable($dummy, 'x', 'User') + [Environment]::SetEnvironmentVariable($dummy, [NullString]::Value, 'User') + return $true +} + +# Add-CiPath makes the CLI available to later steps of a GitHub Actions job. +function Add-CiPath { + param([string]$Dir) + + if ($env:GITHUB_PATH) { + Write-Output $Dir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + } +} + +if (-not $Version) { + $Version = if ($env:FLAGSMITH_CLI_VERSION) { $env:FLAGSMITH_CLI_VERSION } else { $DefaultVersion } +} +if ($Version -notlike 'v*') { $Version = "v$Version" } + +if (-not $BinDir) { + $BinDir = if ($env:FLAGSMITH_INSTALL_DIR) { + $env:FLAGSMITH_INSTALL_DIR + } else { + Join-Path $env:USERPROFILE '.local\bin' + } +} +if ($env:FLAGSMITH_NO_MODIFY_PATH) { $NoModifyPath = $true } + +$arch = Get-TargetArch +$archive = "flagsmith_$($Version.TrimStart('v'))_windows_$arch.zip" +$archiveUrl = "$BaseUrl/$Version/$archive" +$sumsUrl = "$BaseUrl/$Version/checksums.txt" + +if ($DryRun) { + Write-Output "would install flagsmith $Version (windows/$arch) to $BinDir" + Write-Output " archive: $archiveUrl" + Write-Output " checksums: $sumsUrl" + return +} + +# PowerShell 5.1 still defaults to TLS 1.0, which github.com refuses. +if ([Net.ServicePointManager]::SecurityProtocol -notmatch 'Tls12') { + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 +} + +$tmp = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([guid]::NewGuid())) +try { + Write-Output "downloading flagsmith $Version (windows/$arch)" + $zip = Join-Path $tmp $archive + $sums = Join-Path $tmp 'checksums.txt' + try { + Invoke-WebRequest -Uri $archiveUrl -OutFile $zip -UseBasicParsing + } catch { + throw "cannot download $archiveUrl`nIf $Version was released moments ago its archives may still be uploading - retry shortly, or choose a version with -Version." + } + Invoke-WebRequest -Uri $sumsUrl -OutFile $sums -UseBasicParsing + + $expected = Get-Checksum -SumsFile $sums -Name $archive + $actual = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash + if ($actual -ne $expected.ToUpperInvariant()) { + throw "checksum mismatch for ${archive}: expected $expected, got $actual" + } + + Expand-Archive -LiteralPath $zip -DestinationPath $tmp -Force + New-Item -ItemType Directory -Force -Path $BinDir | Out-Null + Move-Item -Force -LiteralPath (Join-Path $tmp $ExeName) -Destination (Join-Path $BinDir $ExeName) +} finally { + Remove-Item -Recurse -Force -LiteralPath $tmp +} + +$exe = Join-Path $BinDir $ExeName +$installed = & $exe --version +if ($LASTEXITCODE -ne 0) { throw "$exe was installed but will not run" } +Write-Output "installed $installed to $exe" + +$pathAdded = $false +if (-not $NoModifyPath) { + $pathAdded = Add-UserPath -Dir $BinDir + Add-CiPath -Dir $BinDir +} + +Write-Output '' +if ($pathAdded) { + Write-Output "Open a new terminal, then run 'flagsmith init' to get started." +} else { + Write-Output "Run 'flagsmith init' to get started." +} diff --git a/release-please-config.json b/release-please-config.json index 46ba24f..27d669d 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -10,7 +10,8 @@ "draft": false, "include-component-in-tag": false, "extra-files": [ - "install.sh" + "install.sh", + "install.ps1" ] } }, From 3ce15425cab65d041e7bc650a83da13337ca4d3a Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Thu, 30 Jul 2026 13:09:00 +0100 Subject: [PATCH 2/2] Update install.ps1 Co-authored-by: themis-blindfold[bot] <302911107+themis-blindfold[bot]@users.noreply.github.com> --- install.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.ps1 b/install.ps1 index 2e2204e..0e609ad 100644 --- a/install.ps1 +++ b/install.ps1 @@ -105,7 +105,7 @@ if (-not $BinDir) { Join-Path $env:USERPROFILE '.local\bin' } } -if ($env:FLAGSMITH_NO_MODIFY_PATH) { $NoModifyPath = $true } +if ($env:FLAGSMITH_NO_MODIFY_PATH -eq '1') { $NoModifyPath = $true } $arch = Get-TargetArch $archive = "flagsmith_$($Version.TrimStart('v'))_windows_$arch.zip"