Repository navigation
feat: add get-pnpm, the installer as an npm package #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
d4430b7
feat: add get-pnpm, the installer as an npm package
zkochan f434d0e
fix: harden the verification path against the review findings
zkochan 5c30dd0
refactor: split downloadPnpm out of installPnpm, rename the directory
zkochan c64af8c
chore: set the first publishable version
zkochan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: get-pnpm package | ||
|
|
||
| on: | ||
| - push | ||
| - pull_request | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| test: | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: | ||
| - ubuntu-latest | ||
| - macos-latest | ||
| - windows-latest | ||
| runtime: | ||
| # The oldest supported runtime and the current one. `engines` allows | ||
| # Node 22, so it has to be exercised there. | ||
| - node@22 | ||
| - node@24 | ||
|
|
||
| runs-on: ${{ matrix.os }} | ||
| defaults: | ||
| run: | ||
| working-directory: get-pnpm | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| # pnpm's own action, installing pnpm and the runtime in one step. Nothing | ||
| # to install at the repository root, so its `pnpm install` is left off. | ||
| - uses: pnpm/setup@v2 | ||
| with: | ||
| version: latest | ||
| runtime: ${{ matrix.runtime }} | ||
| install: false | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
| - run: pnpm test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| name: npm signing keys | ||
|
|
||
| on: | ||
| schedule: | ||
| # Weekly. A rotation npm has not announced would otherwise surface as a | ||
| # failed install for whoever runs the installer next. | ||
| - cron: '0 6 * * 1' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| # One rotation at a time: a manual run alongside the weekly one would otherwise | ||
| # race over the same branch. | ||
| concurrency: | ||
| group: npm-key-rotation | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| refresh: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
|
|
||
| # The script only needs a runtime; pnpm comes along with it. | ||
| - uses: pnpm/setup@v2 | ||
| with: | ||
| version: latest | ||
| runtime: node@24 | ||
| install: false | ||
|
|
||
| - id: refresh | ||
| run: | | ||
| if node scripts/update-npm-keys.mjs; then | ||
| echo "changed=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| node scripts/update-npm-keys.mjs --update | ||
| echo "changed=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - if: steps.refresh.outputs.changed == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # One long-lived branch, force-pushed: a weekly job must not open a | ||
| # second pull request for a rotation that is already waiting, and a | ||
| # re-run on the same day must not collide with its own branch. | ||
| branch=npm-key-rotation | ||
| git config user.name 'github-actions[bot]' | ||
| git config user.email 'github-actions[bot]@users.noreply.github.com' | ||
| git checkout -B "$branch" | ||
| git commit -am 'chore: refresh the pinned npm signing keys' | ||
| git push --force origin "$branch" | ||
| if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then | ||
| gh pr create --head "$branch" --title 'chore: refresh the pinned npm signing keys' --body \ | ||
| 'npm rotated its registry signing key. Every installer pins it, so all three files change together. | ||
|
|
||
| The checksums in `SHASUMS256.txt` cover `install.sh` and `install.ps1`, so this needs a re-sign (`./_update.sh`) before merge.' | ||
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,3 @@ | ||
| .claude/ | ||
| get-pnpm/lib | ||
| get-pnpm/node_modules |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # get-pnpm | ||
|
|
||
| > Installs pnpm as a standalone executable | ||
|
|
||
| Installs pnpm the same way [the standalone install script](https://github.com/pnpm/get.pnpm.io) does — a self-contained executable in `PNPM_HOME`, on your `PATH`, with no dependency on Node.js afterwards — for people who would rather not pipe a script into a shell. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```sh | ||
| npx get-pnpm | ||
| ``` | ||
|
|
||
| Install a specific version, major, or dist-tag: | ||
|
|
||
| ```sh | ||
| npx get-pnpm 12 # the latest release of pnpm 12 | ||
| npx get-pnpm next-12 # the pnpm 12 prerelease lane | ||
| npx get-pnpm 11.20.0 # an exact version | ||
| ``` | ||
|
|
||
| Then open a new terminal, or source the file the installer names in its output. | ||
|
|
||
| ## How it works | ||
|
|
||
| 1. Resolves the requested version against the `pnpm` dist-tags. | ||
| 2. Downloads the executable for your platform from the npm registry. | ||
| 3. Checks npm's signature over the package's checksum, then the download against that checksum. | ||
| 4. Runs `pnpm setup`, which installs the executable globally and adds `PNPM_HOME` to your `PATH`. | ||
|
|
||
| Step 3 is what makes step 2 worth trusting. The registry publishes both the tarball | ||
| and its checksum, so a checksum taken from it proves nothing on its own; npm also | ||
| signs `<name>@<version>:<integrity>` with a key that this package pins, and a | ||
| download that fails either check is refused rather than installed. | ||
|
|
||
| The download goes to the registry npm is configured with (`npm_config_registry`), not to GitHub. Registries that require authentication are not supported yet. | ||
|
|
||
| ## Environment variables | ||
|
|
||
| | Variable | Description | | ||
| | --- | --- | | ||
| | `PNPM_VERSION` | Version to install when no argument is given. | | ||
| | `PNPM_HOME` | Directory to install pnpm into. | | ||
| | `npm_config_registry` | Registry to download pnpm from. | | ||
|
|
||
| ## Using it from a program | ||
|
|
||
| `installPnpm` does what the command does: download, verify, then hand over to | ||
| `pnpm setup`, which installs globally and edits your `PATH`. A caller that | ||
| manages its own directory — a CI action, say — wants `downloadPnpm` instead, | ||
| which verifies and places the executable and nothing else: | ||
|
|
||
| ```js | ||
| import { downloadPnpm } from 'get-pnpm' | ||
|
|
||
| const { version, binPath } = await downloadPnpm({ | ||
| versionSpec: 'next-12', | ||
| registry: 'https://registry.npmjs.org/', | ||
| dest: '/opt/pnpm', | ||
| }) | ||
| ``` | ||
|
|
||
| ## Development | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm test # builds, then runs the tests against the build output | ||
| ``` | ||
|
|
||
| The published package runs on Node.js 22.13 and newer. The test runner needs | ||
| 22.18 or newer, which is where Node runs TypeScript without a flag. | ||
|
|
||
| ## Other ways to install pnpm | ||
|
|
||
| See [pnpm.io/installation](https://pnpm.io/installation). The shell and PowerShell | ||
| installers in this repository do the same job for machines without Node.js, and pin | ||
| the same npm signing key. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #!/usr/bin/env node | ||
| import { runCli } from '../lib/index.js' | ||
|
|
||
| try { | ||
| process.exitCode = await runCli(process.argv.slice(2)) | ||
| } catch (err) { | ||
| // Anything can be thrown, and reading `.message` off a non-Error would either | ||
| // print `undefined` or throw again, burying the reason under a stack trace. | ||
| console.error(err instanceof Error ? err.message : String(err)) | ||
| process.exitCode = 1 | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| { | ||
| "name": "get-pnpm", | ||
| "version": "0.0.1", | ||
| "description": "Installs pnpm as a standalone executable", | ||
| "keywords": [ | ||
| "pnpm", | ||
| "install", | ||
| "installer" | ||
| ], | ||
| "license": "MIT", | ||
| "funding": "https://opencollective.com/pnpm", | ||
| "repository": "https://github.com/pnpm/get.pnpm.io/tree/main/get-pnpm", | ||
| "homepage": "https://github.com/pnpm/get.pnpm.io/tree/main/get-pnpm#readme", | ||
| "bugs": { | ||
| "url": "https://github.com/pnpm/get.pnpm.io/issues" | ||
| }, | ||
| "type": "module", | ||
| "main": "lib/index.js", | ||
| "types": "lib/index.d.ts", | ||
| "exports": { | ||
| ".": "./lib/index.js" | ||
| }, | ||
| "files": [ | ||
| "lib", | ||
| "!*.map", | ||
| "bin" | ||
| ], | ||
| "bin": { | ||
| "get-pnpm": "bin/get-pnpm.js" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "tsc && node --test test/*.test.ts", | ||
| "prepublishOnly": "tsc" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^24.10.1", | ||
| "typescript": "^5.9.3" | ||
| }, | ||
| "engines": { | ||
| "node": ">=22.13" | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { spawnSync } from 'node:child_process' | ||
| import fs from 'node:fs' | ||
|
|
||
| /** | ||
| * Unpacks a package tarball into `dest`, leaving its `package/` root in place. | ||
| * | ||
| * Shells out to `tar`, which every supported host has: macOS and Linux ship it, | ||
| * and Windows has had bsdtar since Windows 10 1803. Member selection and | ||
| * `--strip-components` are avoided because busybox tar and bsdtar disagree | ||
| * about them. | ||
| */ | ||
| export function extractTarball (tarball: string, dest: string): void { | ||
| fs.mkdirSync(dest, { recursive: true }) | ||
| const { error, status, stderr } = spawnSync('tar', ['-xzf', tarball, '-C', dest], { encoding: 'utf8' }) | ||
| if (error != null) { | ||
| if ((error as NodeJS.ErrnoException).code === 'ENOENT') { | ||
| throw new Error('This installer needs the `tar` command, which was not found on your PATH.') | ||
| } | ||
| throw error | ||
| } | ||
| if (status !== 0) { | ||
| throw new Error(`Could not extract ${tarball}: ${stderr.trim()}`) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.