1
0
mirror of https://github.com/pnpm/action-setup synced 2026-05-05 03:07:48 +02:00
Files
action-setup/src/install-pnpm/ensureAliasLinks.ts
Zoltan Kochan aec59b9f6c fix: fall back to pnpm shim in same directory for self-update bin/
ensureAliasLinks had a hardcoded relative path to @pnpm/exe/pnpm which
only works from node_modules/.bin/. In self-update's bin/ directory
($PNPM_HOME/bin/), that path doesn't resolve. Now falls back to using
the pnpm shim in the same directory when the package path doesn't exist.
2026-03-26 20:24:03 +01:00

85 lines
3.0 KiB
TypeScript

import { unlink, writeFile, symlink } from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
function shScript (command: string): string {
return `#!/bin/sh\nexec ${command} "$@"\n`
}
function cmdShim (command: string): string {
return `@ECHO off\r\n${command} %*\r\n`
}
function pwshShim (command: string): string {
return `#!/usr/bin/env pwsh\n${command} @args\n`
}
async function forceSymlink (target: string, linkPath: string): Promise<void> {
try { await unlink(linkPath) } catch {}
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 })
}
/**
* Find the pnpm binary/shim relative to binDir.
* Checks the package directory first (node_modules/.bin/../@pnpm/exe/pnpm),
* then falls back to a pnpm shim in binDir itself (e.g. self-update's bin/).
*/
function findPnpmTarget (binDir: string, standalone: boolean): string | undefined {
const packageTarget = standalone
? path.join('..', '@pnpm', 'exe', 'pnpm')
: path.join('..', 'pnpm', 'bin', 'pnpm.cjs')
if (existsSync(path.resolve(binDir, packageTarget))) {
return packageTarget
}
// self-update creates a pnpm shim in $PNPM_HOME/bin/ — use it directly
if (existsSync(path.join(binDir, 'pnpm'))) {
return 'pnpm'
}
return undefined
}
/**
* Create pn/pnpx/pnx alias links in the bin directory.
*
* 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".
*/
export async function ensureAliasLinks (binDir: string, standalone: boolean, platform: NodeJS.Platform = process.platform): Promise<void> {
const isWindows = platform === 'win32'
const pnpmTarget = findPnpmTarget(binDir, standalone)
if (!pnpmTarget) 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)
}
}
}