Skip to content

feat: support pnpm v11 tarball format and add post-install verification#44

Merged
zkochan merged 4 commits intomainfrom
support-pnpm-11
Feb 22, 2026
Merged

feat: support pnpm v11 tarball format and add post-install verification#44
zkochan merged 4 commits intomainfrom
support-pnpm-11

Conversation

@zkochan
Copy link
Copy Markdown
Member

@zkochan zkochan commented Feb 21, 2026

pnpm v11+ distributes standalone executables as tarballs (tar.gz on Unix, zip on Windows) containing the binary and a dist/ directory, replacing the previous single-binary distribution format.

  • install.sh: detect v11+ by major version and extract tarball instead of downloading a single binary; older versions use the previous code path
  • install.ps1: same logic using Expand-Archive (Windows) or tar (Unix)
  • CI workflows: set PNPM_HOME to a known path and add a verification step that runs pnpm add webpack in a temp dir and asserts that node_modules/webpack/package.json was created

pnpm v11+ distributes standalone executables as tarballs (tar.gz on Unix,
zip on Windows) containing the binary and a dist/ directory, replacing the
previous single-binary distribution format.

- install.sh: detect v11+ by major version and extract tarball instead of
  downloading a single binary; older versions use the previous code path
- install.ps1: same logic using Expand-Archive (Windows) or tar (Unix)
- CI workflows: set PNPM_HOME to a known path and add a verification step
  that runs `pnpm add webpack` in a temp dir and asserts that
  node_modules/webpack/package.json was created

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
get-pnpm-io Ready Ready Preview, Comment Feb 22, 2026 10:56pm

Request Review

@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 21, 2026

Deploy Preview for getpnpm ready!

Name Link
🔨 Latest commit 017a322
🔍 Latest deploy log https://app.netlify.com/projects/getpnpm/deploys/699b897721f59d000871e801
😎 Deploy Preview https://deploy-preview-44--getpnpm.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Add a pnpm_version matrix dimension with two entries:
- 10.30.1: tests the single-binary download code path (v10 and older)
- next-11: tests the tarball download code path (v11+)

For shell-based workflows, a "Resolve pnpm version" step converts dist-tags
like next-11 to a concrete version number before passing it to install.sh
(which does not resolve dist-tags itself). For the PowerShell workflow,
install.ps1 already resolves dist-tags natively so PNPM_VERSION is passed
through directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The v11 tarball extraction can produce files with read-only
permissions, causing Remove-Item to fail. Adding -Force allows
deletion of such files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the pnpm installer scripts and CI to handle pnpm v11+ standalone release artifacts (tar.gz/zip containing dist/) and adds a smoke test to verify pnpm can install a package after installation.

Changes:

  • Update install.sh / install.ps1 to download and extract v11+ tarball/zip releases, while keeping the legacy single-binary path for v10 and below.
  • Add CI matrices to test both v10 and v11 install paths, and add a post-install verification step (pnpm add webpack).
  • Refresh checksum artifacts for the modified installer scripts and add SHASUMS256.txt.sig.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
install.sh Adds v11+ tarball/zip extraction logic based on major version.
install.ps1 Adds v11+ tarball/zip extraction logic based on major version, keeping dist-tag resolution.
SHASUMS256.txt Updates hashes for the modified installer scripts.
SHASUMS256.txt.sig Adds detached GPG signature for SHASUMS256.txt.
.github/workflows/powershell.yml Adds pnpm version matrix and post-install verification for PowerShell runs.
.github/workflows/install-ubuntu.yml Adds pnpm version matrix, tag resolution, and post-install verification.
.github/workflows/install-macos.yml Adds pnpm version matrix, tag resolution, and post-install verification.
.github/workflows/install-alpine.yml Adds pnpm version matrix, tag resolution, and post-install verification in Alpine container.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install.sh
major_version="$(echo "$version" | cut -d. -f1)"
if [ "$major_version" -ge 11 ] 2>/dev/null; then
# v11+: distributed as tarballs containing the binary and dist/ directory
if [ "${platform}" = "win" ]; then
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v11+ Windows (mingw/msys) path relies on unzip, but the script doesn’t check that unzip is available. If detect_platform returns win in a POSIX shell environment without unzip, the install will fail with a generic “command not found”. Consider adding an explicit tool check with a clear abort message, or using an extraction method that’s guaranteed to exist in the supported environments.

Suggested change
if [ "${platform}" = "win" ]; then
if [ "${platform}" = "win" ]; then
if ! command -v unzip >/dev/null 2>&1; then
abort "The 'unzip' command is required to install pnpm v${version} on Windows (mingw/msys). Please install 'unzip' and re-run this script."
fi

Copilot uses AI. Check for mistakes.
Comment thread install.ps1
Comment on lines +138 to +142
$tempArchive = Join-Path $tempFileFolder.FullName "pnpm.tar.gz"
Invoke-WebRequest $archiveUrl -OutFile $tempArchive -UseBasicParsing
tar -xzf $tempArchive -C $tempFileFolder.FullName
$tempFile = Join-Path $tempFileFolder.FullName "pnpm"
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguments passed to external commands here aren’t quoted. If the temp path ever contains spaces, tar will interpret it as multiple args and extraction will fail. Use quoted arguments (or --% where appropriate) for $tempArchive and $tempFileFolder.FullName (and similarly for $tempFile in chmod).

Copilot uses AI. Check for mistakes.
echo "PNPM_VERSION=$tag" >> "$GITHUB_ENV"
else
json=$(curl -fsSL "https://registry.npmjs.org/@pnpm/exe")
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the dist-tag lookup fails, version can become empty and the workflow will still proceed, causing the installer to fall back to latest (or fail later) rather than failing fast. Add a check that the resolved version is non-empty and exit with an error if it can’t be resolved.

Suggested change
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
if [ -z "$version" ]; then
echo "Error: Failed to resolve pnpm version for dist-tag '$tag'." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
echo "PNPM_VERSION=$tag" >> "$GITHUB_ENV"
else
json=$(curl -fsSL "https://registry.npmjs.org/@pnpm/exe")
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the dist-tag lookup fails, version can become empty and the workflow will still proceed, causing the installer to fall back to latest (or fail later) rather than failing fast. Add a check that the resolved version is non-empty and exit with an error if it can’t be resolved.

Suggested change
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
if [ -z "$version" ]; then
echo "Failed to resolve pnpm version for tag '$tag'" >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
else
json=$(wget -qO- "https://registry.npmjs.org/@pnpm/exe")
fi
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the dist-tag lookup fails, version can become empty and the workflow will still proceed, causing the installer to fall back to latest (or fail later) rather than failing fast. Add a check that the resolved version is non-empty and exit with an error if it can’t be resolved.

Suggested change
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
version=$(echo "$json" | grep -o "\"$tag\":[[:space:]]*\"[^\"]*\"" | grep -o '"[0-9][^"]*"' | tr -d '"')
if [ -z "$version" ]; then
echo "Error: Failed to resolve pnpm version for dist-tag '$tag'." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
Comment thread install.sh
Comment on lines +106 to +123
download "https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${platform}-${arch}.zip" > "$tmp_dir/pnpm.zip" || return 1
unzip -q "$tmp_dir/pnpm.zip" -d "$tmp_dir" || return 1
SHELL="$SHELL" "$tmp_dir/pnpm.exe" setup --force || return 1
else
download "https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${platform}-${arch}.tar.gz" > "$tmp_dir/pnpm.tar.gz" || return 1
tar -xzf "$tmp_dir/pnpm.tar.gz" -C "$tmp_dir" || return 1
chmod +x "$tmp_dir/pnpm"
SHELL="$SHELL" "$tmp_dir/pnpm" setup --force || return 1
fi
else
# older versions: distributed as a single executable binary
local archive_url
archive_url="https://github.com/pnpm/pnpm/releases/download/v${version}/pnpm-${platform}-${arch}"
if [ "${platform}" = "win" ]; then
archive_url="${archive_url}.exe"
fi
download "$archive_url" > "$tmp_dir/pnpm" || return 1
chmod +x "$tmp_dir/pnpm"
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This installer downloads pnpm binaries from GitHub and executes them (download followed by SHELL="$SHELL" "$tmp_dir/pnpm" setup) without any checksum or signature verification, which enables a supply-chain attack if the release asset or TLS channel is compromised. An attacker who can tamper with the GitHub release (e.g., account compromise, repo compromise, or MITM) can deliver a malicious pnpm binary that will be run with the user's privileges. To mitigate this, verify the downloaded asset against a trusted checksum or GPG signature (for example using the SHASUMS256.txt/.sig files) before making it executable and running it.

Copilot uses AI. Check for mistakes.
Comment thread install.ps1
Comment on lines +131 to +150
$archiveUrl = "https://github.com/pnpm/pnpm/releases/download/v$version/pnpm-$platform-$architecture.zip"
$tempArchive = Join-Path $tempFileFolder.FullName "pnpm.zip"
Invoke-WebRequest $archiveUrl -OutFile $tempArchive -UseBasicParsing
Expand-Archive -Path $tempArchive -DestinationPath $tempFileFolder.FullName -Force
$tempFile = Join-Path $tempFileFolder.FullName "pnpm.exe"
} else {
$archiveUrl = "https://github.com/pnpm/pnpm/releases/download/v$version/pnpm-$platform-$architecture.tar.gz"
$tempArchive = Join-Path $tempFileFolder.FullName "pnpm.tar.gz"
Invoke-WebRequest $archiveUrl -OutFile $tempArchive -UseBasicParsing
tar -xzf $tempArchive -C $tempFileFolder.FullName
$tempFile = Join-Path $tempFileFolder.FullName "pnpm"
}
} else {
# older versions: distributed as a single executable binary
$archiveUrl = "https://github.com/pnpm/pnpm/releases/download/v$version/pnpm-$platform-$architecture"
if ($platform -eq 'win') {
$archiveUrl = "$archiveUrl.exe"
}
$tempFile = Join-Path $tempFileFolder.FullName $pnpmName
Invoke-WebRequest $archiveUrl -OutFile $tempFile -UseBasicParsing
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PowerShell installer downloads pnpm binaries from GitHub (Invoke-WebRequest into a temp path) and later executes the resulting file via Start-Process without any checksum or signature verification, which exposes users to supply-chain attacks if the release asset or TLS channel is compromised. A malicious or tampered release archive could provide a trojan pnpm binary that will be run with the caller's privileges. To harden this, validate the downloaded archive or binary against a trusted checksum or GPG signature before extraction and execution, instead of trusting the network and hosting service alone.

Copilot uses AI. Check for mistakes.
@zkochan zkochan merged commit a924107 into main Feb 22, 2026
78 of 84 checks passed
@zkochan zkochan deleted the support-pnpm-11 branch February 23, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants