1
0
mirror of https://github.com/pnpm/action-setup synced 2026-05-12 16:21:08 +02:00

fix: point pn to pnpm binary directly, not to @pnpm/exe/pn

pnpm self-update only replaces the pnpm binary — it does not update
other files in the @pnpm/exe package (setup.js, pn, pnpx, pnx all
remain from the v10 bootstrap). So @pnpm/exe/pn may not exist at all.

Instead of relying on @pnpm/exe/pn, create the aliases directly:
- pn → symlink to the pnpm binary
- pnpx/pnx → shell scripts that exec "pnpm dlx"

Also remove the setup.js call after self-update since it's no longer
needed and would run the v10 version which doesn't know about pn.
This commit is contained in:
Zoltan Kochan
2026-03-26 18:58:55 +01:00
parent b179ac1ba6
commit 188c8307ce
4 changed files with 243 additions and 240 deletions

View File

@@ -2,32 +2,16 @@ import { unlink, writeFile, symlink } from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
interface AliasDefinition {
name: string
target: string
function shScript (command: string): string {
return `#!/bin/sh\nexec ${command} "$@"\n`
}
function getAliases (standalone: boolean): AliasDefinition[] {
if (standalone) {
return [
{ name: 'pn', target: path.join('..', '@pnpm', 'exe', 'pn') },
{ name: 'pnpx', target: path.join('..', '@pnpm', 'exe', 'pnpx') },
{ name: 'pnx', target: path.join('..', '@pnpm', 'exe', 'pnx') },
]
}
return [
{ name: 'pn', target: path.join('..', 'pnpm', 'bin', 'pnpm.cjs') },
{ name: 'pnpx', target: path.join('..', 'pnpm', 'bin', 'pnpx.cjs') },
{ name: 'pnx', target: path.join('..', 'pnpm', 'bin', 'pnpx.cjs') },
]
function cmdShim (command: string): string {
return `@ECHO off\r\n${command} %*\r\n`
}
function cmdShim (target: string): string {
return `@ECHO off\r\n"%~dp0\\${target}" %*\r\n`
}
function pwshShim (target: string): string {
return `#!/usr/bin/env pwsh\n& "$PSScriptRoot\\${target}" @args\n`
function pwshShim (command: string): string {
return `#!/usr/bin/env pwsh\n${command} @args\n`
}
async function forceSymlink (target: string, linkPath: string): Promise<void> {
@@ -35,27 +19,52 @@ async function forceSymlink (target: string, linkPath: string): Promise<void> {
await symlink(target, linkPath)
}
async function forceWriteFile (filePath: string, content: string, mode?: number): Promise<void> {
try { await unlink(filePath) } catch {}
await writeFile(filePath, content, { mode })
}
/**
* Create pn/pnpx/pnx alias links in the bin directory.
* On Unix, creates symlinks. On Windows, creates .cmd and .ps1 shims.
* Only creates links when the target file actually exists (pnpm v11+).
*
* Existing links are always replaced because npm may have created shims
* pointing to an isolated .tools/ copy that has stale placeholder files.
* pn is an alias for pnpm, so it symlinks (or shims) to the pnpm binary.
* pnpx/pnx are aliases for "pnpm dlx", created as shell scripts.
*
* This does NOT rely on the @pnpm/exe package having pn/pnx files, because
* pnpm self-update only replaces the pnpm binary — it doesn't update other
* files in the package. The aliases are created by pointing pn directly to
* the pnpm binary, and pnpx/pnx as scripts that exec "pnpm dlx".
*
* Only creates links when the pnpm binary exists in the expected location
* (i.e. the package has been installed). This is always true after bootstrap.
*/
export async function ensureAliasLinks (binDir: string, standalone: boolean, platform: NodeJS.Platform = process.platform): Promise<void> {
const aliases = getAliases(standalone)
const isWindows = platform === 'win32'
for (const { name, target } of aliases) {
const resolvedTarget = path.resolve(binDir, target)
if (!existsSync(resolvedTarget)) continue
// Determine the pnpm binary path relative to binDir
const pnpmTarget = standalone
? path.join('..', '@pnpm', 'exe', 'pnpm')
: path.join('..', 'pnpm', 'bin', 'pnpm.cjs')
if (isWindows) {
await writeFile(path.join(binDir, `${name}.cmd`), cmdShim(target))
await writeFile(path.join(binDir, `${name}.ps1`), pwshShim(target))
} else {
await forceSymlink(target, path.join(binDir, name))
const resolvedPnpm = path.resolve(binDir, pnpmTarget)
if (!existsSync(resolvedPnpm)) return
if (isWindows) {
// pn → calls pnpm directly
await writeFile(path.join(binDir, 'pn.cmd'), cmdShim(`"%~dp0\\${pnpmTarget}"`))
await writeFile(path.join(binDir, 'pn.ps1'), pwshShim(`& "$PSScriptRoot\\${pnpmTarget}"`))
// pnpx/pnx → calls pnpm dlx
for (const name of ['pnpx', 'pnx']) {
await writeFile(path.join(binDir, `${name}.cmd`), cmdShim(`"%~dp0\\${pnpmTarget}" dlx`))
await writeFile(path.join(binDir, `${name}.ps1`), pwshShim(`& "$PSScriptRoot\\${pnpmTarget}" dlx`))
}
} else {
// pn → symlink to pnpm binary
await forceSymlink(pnpmTarget, path.join(binDir, 'pn'))
// pnpx/pnx → shell scripts that exec pnpm dlx
for (const name of ['pnpx', 'pnx']) {
const pnpmPath = `"$(dirname "$0")/${pnpmTarget}"`
await forceWriteFile(path.join(binDir, name), shScript(`${pnpmPath} dlx`), 0o755)
}
}
}