From 269504758dbd9b09a65635a90cfbda5182e03342 Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 16 Sep 2026 15:27:12 -0400 Subject: [PATCH 1/2] Accept a pinned version with or without the leading v MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported on #27. The channel's directories are named `v0.2.1`, and `MAPBOX_CLI_VERSION` went into the URL untouched — so the spelling a person would actually copy failed: $ MAPBOX_CLI_VERSION=0.2.1 … curl: (56) The requested URL returned error: 403 mapbox-cli: could not read https://cli.mapbox.com/0.2.1/manifest.json Every place a version is read from shows it without the `v`: `mapbox --version`, CHANGELOG.md, Cargo.toml. So the one spelling that worked was the one nobody sees, and the failure was a bare S3 `403`, which reads as "you are not allowed" rather than "no such version". The review asked for a note about it. A note documents a trap; this removes it. Both installers now prepend the `v` when the value starts with a digit, which leaves `latest` — and any other channel name — alone, since only a leading digit means a version number is being named. Verified against the live channel: `0.2.1`, `v0.2.1` and `latest` all install 0.2.1. Two cases added to each installer suite, including one pinning `latest` because getting that wrong would break the default install rather than an edge case. Both confirmed to fail with the fix reverted. **And the variable is now documented at all**, which is the other half of this. `MAPBOX_CLI_VERSION` appeared in no `.md` in the repository — the reviewer on #26 suggested using it, which is how I found that a reader had no way to know it exists. It goes in the install section with `MAPBOX_INSTALL_DIR`, which was also undocumented. 601 tests, both installer suites green, fmt clean, no broken anchors. --- CHANGELOG.md | 12 ++++++++++++ README.md | 15 +++++++++++++++ scripts/install.ps1 | 7 +++++++ scripts/install.sh | 14 ++++++++++++++ scripts/test-install.ps1 | 21 +++++++++++++++++++++ scripts/test-install.sh | 23 +++++++++++++++++++++++ 6 files changed, 92 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6030012..9bf8cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,18 @@ that may never merge. They are not releases and are not listed here. parameter the request never carried, and anyone pasting it sent something different from what was being debugged. +- `MAPBOX_CLI_VERSION` now accepts a version with or without the leading `v`. + The channel's directories are named `v0.2.1`, but every place a person reads + a version from shows it without one — `mapbox --version`, this file, + `Cargo.toml` — so the spelling somebody would copy was the one that failed, + and it failed as a bare `403` from S3 on a path that does not exist. That + reads as "you are not allowed" rather than "no such version". `latest` and + any other non-numeric channel name are untouched. Both installers, both + covered by their suites. + +- `MAPBOX_CLI_VERSION` and `MAPBOX_INSTALL_DIR` are documented in the README, + which never mentioned either of them. + ## 0.2.2 - 2026-09-15 ### Changed diff --git a/README.md b/README.md index 872721e..2e3e67c 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,21 @@ curl -fsSL https://cli.mapbox.com/install.sh | sh irm https://cli.mapbox.com/install.ps1 | iex ``` +`MAPBOX_CLI_VERSION` pins a version instead of taking the newest: + +```sh +curl -fsSL https://cli.mapbox.com/install.sh | MAPBOX_CLI_VERSION=0.2.1 sh +``` + +```powershell +$env:MAPBOX_CLI_VERSION = '0.2.1'; irm https://cli.mapbox.com/install.ps1 | iex +``` + +With or without the leading `v`: `0.2.1` and `v0.2.1` both work, so the +version `mapbox --version` prints can be pasted straight in. +`MAPBOX_INSTALL_DIR` chooses where the binary lands, and defaults to +`~/.local/bin`. + `scripts/install.sh` and `scripts/install.ps1` here are those installers' sources; `scripts/test-install.sh` and `scripts/test-install.ps1` exercise them end to end without touching the network. diff --git a/scripts/install.ps1 b/scripts/install.ps1 index cbd81b8..e9364a0 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -56,6 +56,13 @@ $Version = 'latest' if ($env:MAPBOX_CLI_VERSION) { $Version = $env:MAPBOX_CLI_VERSION } + # Accepted with or without the leading `v`, same as install.sh. The + # channel names its directories `v0.2.1`, but `mapbox --version`, + # CHANGELOG.md and Cargo.toml all show the version without one, so the + # spelling a person copies is the one that used to 403. `latest` and any + # other non-numeric channel name is left alone. + if ($Version -match '^[0-9]') { $Version = "v$Version" } + $InstallDir = $env:MAPBOX_INSTALL_DIR # Only set if you need a non-production channel. Set MAPBOX_CLI_AUTH to diff --git a/scripts/install.sh b/scripts/install.sh index 243d5b0..7ee88cf 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -21,6 +21,20 @@ set -eu BASE_URL="${MAPBOX_CLI_BASE_URL:-__MAPBOX_CLI_BASE_URL__}" VERSION="${MAPBOX_CLI_VERSION:-latest}" + +# A pinned version is accepted with or without the leading `v`. +# +# The channel's directories are named `v0.2.1`, but every place a person reads +# a version from shows it without one: `mapbox --version`, CHANGELOG.md, +# Cargo.toml. So the spelling somebody copies is the spelling that used to +# fail, and it failed as `403` from S3 on a path that does not exist — which +# reads as "you are not allowed" rather than "no such version". +# +# `latest` and anything else non-numeric is left alone: only a leading digit +# means a version number is being named. +case "$VERSION" in + [0-9]*) VERSION="v${VERSION}" ;; +esac INSTALL_DIR="${MAPBOX_INSTALL_DIR:-$HOME/.local/bin}" # Where a reader is sent to build from source or report a bad artifact. One diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index 1a04678..4ef4594 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -598,6 +598,27 @@ try { Expect-Out 'Installed mapbox 0.1.0-dev.abc1234' 'installs that exact version' Expect-Out "channel $PinnedVersion" 'names the channel it resolved' + # The channel's directories carry a leading `v`. Every place a person + # reads a version from — `mapbox --version`, CHANGELOG.md, Cargo.toml — + # shows it without one, so the spelling somebody copies has to work. It + # used to 403, which reads as "not allowed" rather than "no such version". + Start-Case 'MAPBOX_CLI_VERSION accepts a version without the leading v' + New-CaseEnv 'pinned-bare' + $env:MAPBOX_CLI_VERSION = $PinnedVersion -replace '^v', '' + Invoke-Installer + Expect-Status 0 'exits 0' + Expect-Out 'Installed mapbox 0.1.0-dev.abc1234' 'installs that exact version' + Expect-Out "channel $PinnedVersion" 'and resolved the v-prefixed directory' + + # `latest` starts with a letter, so nothing is prepended. Getting this + # wrong would break the default install rather than an edge case. + Start-Case 'a channel name that is not a version is left alone' + New-CaseEnv 'pinned-latest' + $env:MAPBOX_CLI_VERSION = 'latest' + Invoke-Installer + Expect-Status 0 'exits 0' + Expect-Out 'channel latest' 'asked for latest, not vlatest' + Start-Case 'reinstalling reports the version it replaced' New-CaseEnv 'upgrade' New-FakeBinary (Join-Path $script:BinDir 'mapbox.exe') 'mapbox 0.0.1' diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 37a6f84..30ad9b1 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -756,6 +756,29 @@ expect_status 0 "$status" 'exits 0' expect_out 'Installed mapbox 0.1.0-dev.abc1234' 'installs that exact version' expect_out 'channel v0.1.0-dev.abc1234' 'names the channel it resolved' +# The channel's directories carry a leading `v`. Every place a person reads a +# version from — `mapbox --version`, CHANGELOG.md, Cargo.toml — shows it +# without one, so the spelling somebody copies is the one that has to work. +# It used to 403, which reads as "not allowed" rather than "no such version". +start 'MAPBOX_CLI_VERSION accepts a version without the leading v' +new_case_env pinned-bare +export MAPBOX_CLI_VERSION=0.1.0-dev.abc1234 +export MAPBOX_INSTALL_TILESETS=no +run_piped && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_out 'Installed mapbox 0.1.0-dev.abc1234' 'installs that exact version' +expect_out 'channel v0.1.0-dev.abc1234' 'and resolved the v-prefixed directory' + +# `latest` starts with a letter, so nothing is prepended to it. Pinning this +# wrong would break the default install rather than an edge case. +start 'a channel name that is not a version is left alone' +new_case_env pinned-latest +export MAPBOX_CLI_VERSION=latest +export MAPBOX_INSTALL_TILESETS=no +run_piped && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_out 'channel latest' 'asked for latest, not vlatest' + start 'an unsupported platform stops before downloading' new_case_env unsupported shim uname-unsupported uname From 4c6f14eaafed493f0de01b4930875adc90a31f6a Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Wed, 16 Sep 2026 15:31:27 -0400 Subject: [PATCH 2/2] Keep test-install.ps1 ASCII, which PSScriptAnalyzer requires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI went red on the previous commit for a reason worth recording rather than just fixing: `PSUseBOMForUnicodeEncodedFile` fails a non-ASCII `.ps1` that carries no BOM, and the em-dash in my new comment was the first non-ASCII byte the file had ever contained. Both `.ps1` files were ASCII-clean before this branch and are again. The comment says so, so the next person writing prose in there knows the constraint exists before CI tells them. Also ran PSScriptAnalyzer locally with ci.yml's exact rule set — the same exclusions and the same `PSUseCompatibleSyntax` 5.1/7.0 pair — rather than pushing and hoping. Clean on both files. --- scripts/test-install.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index 4ef4594..a07c36c 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -599,9 +599,13 @@ try { Expect-Out "channel $PinnedVersion" 'names the channel it resolved' # The channel's directories carry a leading `v`. Every place a person - # reads a version from — `mapbox --version`, CHANGELOG.md, Cargo.toml — + # reads a version from (`mapbox --version`, CHANGELOG.md, Cargo.toml) # shows it without one, so the spelling somebody copies has to work. It # used to 403, which reads as "not allowed" rather than "no such version". + # + # ASCII only, deliberately: PSScriptAnalyzer's + # PSUseBOMForUnicodeEncodedFile fails a non-ASCII .ps1 that has no BOM, + # and an em-dash in this comment is what turned CI red the first time. Start-Case 'MAPBOX_CLI_VERSION accepts a version without the leading v' New-CaseEnv 'pinned-bare' $env:MAPBOX_CLI_VERSION = $PinnedVersion -replace '^v', ''