corepack
corepack copied to clipboard
corepack disable incorrectly deletes original package manager
Running with Node.js v16.15.0 on macOS
npm --version # 8.5.5
pnpm --version # command not found
npm i -g [email protected]
pnpm --version # 7.1.6
echo '{"packageManager": "[email protected]"}' > package.json
corepack enable pnpm
pnpm --version # 6.32.18
corepack disable pnpm
pnpm --version # command not found
I would expected corepack disable pnpm to revert to the previous global install of 7.1.6
Just ran into this issue too trying to make sure Corepack didn't alter the configuration on my system. Simple test case:
yarn --version # returns version
corepack enable
yarn --version # returns version
corepack disable
yarn --version # command not found
Enabling and disabling Corepack should return the system to the original state.
It seems difficult to do in a way that won't result in other edge cases. For that to work, we'd need to store the links somewhere, so we can restore them. When using nvm or similar tools, this "where" isn't obvious.
Generally, I feel like it's not a huge deal if corepack enable/disable are "destructive" operations. I'm more interested by the reasons why you'd call disable?
I'm more interested by the reasons why you'd call disable?
I created this issue before COREPACK_ENABLE_STRICT=0 existed so I think that will cover my use case.
It was really unexpected to have disable break my system. I was expecting it to work similar to uninstall and put my system back to the way it was before.
we'd need to store the links somewhere
Perhaps keep a backup copy like the following pseudocode:
- enable:
mv bin/pnpm bin/backup/pnpm && ln -s bin/corepack/pnpm bin/pnpm - disable:
rm bin/pnpm && mv bin/backup/pnpm bin/pnpm
Generally, I feel like it's not a huge deal if
corepack enable/disableare "destructive" operations. I'm more interested by the reasons why you'd calldisable?
In general, when trying out a new tool, I try not to use tools that will completely destroy my existing setup. I'm not choosing to use corepack. I'm required to use it because I'm working on a project that uses it in a setup script, so I'm trying to understand what it does.
While it may be the case that once I have everything working, I would use corepack forever and never disable it, it doesn't give one confidence when the default is to destroy your existing configuration when disabling.
I've already been struggling with the docs that state that corepack uses the "latest stable version" rather than the version that was already installed on your machine. I read enough of the documentation to use these as a workaround for that issue:
# Save the current package manger versions as the default versions
command -v yarn &>/dev/null && corepack prepare yarn@`yarn --version` --activate
command -v pnpm &>/dev/null && corepack prepare pnpm@`pnpm --version` --activate
And I now have this as a way to find out if it's enabled or disabled, since corepack doesn't provide that functionality out-of-the-box:
ls -al `command -v yarn` | grep corepack &>/dev/null && echo "Enabled" || echo "Disabled"
The last thing I'm trying to figure out is how to ensure it doesn't kill one of our developers configuration just because they decide to disable it after switching to a different project.
Bottom line: it's a trust issue. If I can't trust that an operation can be reversed, I'm much less likely to want to use that operation in the first place.
Also, one of the reasons I looked into this is that the script in question had this line:
corepack prepare [email protected] --activate
Whoever wrote the script either didn't understand the consequences of --activate making 3.3.0 the default yarn across the entire system (it would have been better named --make-default).
One of our developers ran this script and then had to figure out why all her other projects were running the wrong version of yarn. And since corepack disable deletes yarn entirely, rather than just disabling corepack, there was no easy way to reverse it (if she happened to remember which version of yarn she was running beforehand, she could re-install it, but who remembers exactly which version of yarn their running?)
Part of what I'm doing in learning the issues around using corepack is so I can update this script correctly for others and warn others of the problems running the script (like the fact that it's not reversible).
For those who want a safe way to enable/disable corepack, I wrote this bash script.
With this script, corepack-safe enable will backup the existing environment before enabling corepack and corepack-safe disable will restore the original environment after disabling corepack. enable also sets the default versions within corepack to your original versions, so if you're behind on upgrading a package manager, you won't have an unexpected upgrade to the latest stable version.
This isn't guaranteed to work in all environments, but in theory it should. I tested it with nvm and it'll only affect your active environment. I work on a Mac, so while I added the code for it to work on Windows, I have no way to test it.
Additional commands include:
status
: Shows the corepack status (enabled/disabled) of each package manager
backup
: Backup existing environment only
restore
: Restore existing environment only
restore-all-envs
: Restores all backups in the backup directory, regardless of the current environment (disable corepack globally)
All other commands are passed through to corepack as-is.
#!/usr/bin/env bash
# corepack-safe: safely enable/disable corepack and discover current status
encodePath() {
echo $(curl -Gso /dev/null -w %{url_effective} --data-urlencode "input=$1" "" | sed 's/\/\?input=//')
}
decodePath() {
echo $(printf '%b' "${1//%/\\x}")
}
getInstallFolder() {
if [[ ! -z ${COREPACK_HOME} ]]; then
echo "$COREPACK_HOME"
fi
echo "${XDG_CACHE_HOME:-${LOCALAPPDATA:-$([[ "${OS}" == "Windows_NT" ]] && echo "${HOMEPATH}/AppData/Local" || echo "${HOME}/.cache")}}/node/corepack"
}
getIntalledPackageManagerPaths() {
local packageManagers=()
for packageManager in "${PACKAGE_MANAGERS[@]}"
do
executable=$(command -v $packageManager)
if [ ! -z "$executable" ]; then
packageManagers+=($executable)
fi
done
echo "${packageManagers[@]}"
}
isCorepackEnabledFor() {
executablePath=$(command -v $1)
echo $(readlink "$executablePath" | grep corepack &>/dev/null && echo "y")
}
backupExecutableIfExists() {
executablePath=$(command -v $1)
if [ ! -z "$(isCorepackEnabledFor $1)" ]; then
echo "Skipping backup for $1 -- corepack already enabled"
return 1
fi
if [ ! -z "$executablePath" ]; then
encodedPath=$(encodePath "$executablePath")
linkType=$(readlink "$executablePath" &>/dev/null && echo "link" || echo "file")
# linkPath=$(readlink -f "$executablePath")
mkdir -p "$BACKUP_PATH"
echo "Backing up $executablePath (as $linkType)"
# cp will give an error copying over an existing symbolic link
if [ -h "$BACKUP_PATH/$encodedPath" ]; then
rm "$BACKUP_PATH/$encodedPath"
fi
cp -RP "$executablePath" "$BACKUP_PATH/$encodedPath"
fi
}
backup() {
for packageManager in "${PACKAGE_MANAGERS[@]}"
do
backupExecutableIfExists $packageManager
done
}
restoreExecutableIfExists() {
encodedPath=$(encodePath "$1")
backupPath="$BACKUP_PATH/$encodedPath"
linkType=$(readlink "$backupPath" &>/dev/null && echo "link" || echo "file")
executablePath=$(decodePath "$encodedPath")
if [ -e "$backupPath" ] || [ -h "$backupPath" ]; then
echo "Restoring $executablePath (as $linkType)"
cp -RP "$backupPath" "$executablePath"
fi
}
restore() {
for path in "$@"
do
restoreExecutableIfExists $path
done
}
restoreAll() {
for file in "$BACKUP_PATH"/*; do
if [ -f "$file" ] || [ -L "$file" ]; then
filename=$(basename "$file")
executablePath=$(decodePath "$filename")
restoreExecutableIfExists "$executablePath"
fi
done
}
checkExecutableStatus() {
executablePath=$(command -v $1)
if [ ! -z "$executablePath" ]; then
if [ ! -z "$(isCorepackEnabledFor $1)" ]; then
echo "$1: $executablePath (corepack enabled)";
else
echo "$1: $executablePath (corepack disabled)";
fi
else
echo "$1: not installed"
fi
}
checkStatus() {
for packageManager in "${PACKAGE_MANAGERS[@]}"
do
checkExecutableStatus $packageManager
done
}
setExistingVersionAsDefault() {
executablePath=$(command -v $1)
if [ ! -z "$executablePath" ] && [ -z "$(isCorepackEnabledFor $1)" ]; then
defaultVersion=$($1 --version)
echo "Setting default version of $1 to $defaultVersion"
corepack prepare $1@$defaultVersion --activate
fi
}
setExistingVersionsAsDefaults() {
for packageManager in "${PACKAGE_MANAGERS[@]}"
do
setExistingVersionAsDefault $packageManager
done
}
PACKAGE_MANAGERS=(yarn pnpm)
INSTALL_DIR=$(getInstallFolder)
BACKUP_PATH=${COREPACK_BACKUP_PATH:=$INSTALL_DIR/backups}
case "$1" in
# backups up executables for current environment
backup)
backup
;;
# restores backups for current environment only
restore)
restore $(getIntalledPackageManagerPaths)
;;
# restores all backups saved to backup directory (i.e., all environments)
restore-all-envs)
restoreAll
;;
# Backs up existing environment before enabling corepack in it
enable)
echo "Backing up existing installations to $BACKUP_PATH"
backup
setExistingVersionsAsDefaults
echo "Enabling corepack"
corepack enable
;;
# Disables corepack, then restores original environment
disable)
echo "Disabling corepack"
packageManagerPaths=$(getIntalledPackageManagerPaths)
corepack disable
echo "Restoring original installations from $BACKUP_PATH"
restore $packageManagerPaths
;;
# Shows the corepack enabled status of each package manager
status)
checkStatus
;;
# Delegates to corepack
*)
corepack "$@"
;;
esac
Instead of backing up existing bins/symlinks, how about this?
mkdir -p "$HOME/.corepack/bin"
echo "export PATH=$HOME/.corepack/bin:\$PATH" >> ~/.zshrc
# create corepack shims in my `.corepack` dir, so no system bins will be overridden
corepack enable --install-directory ~/.corepack/bin
# removing the shims now just reveals the previous binaries again
corepack disable --install-directory ~/.corepack/bin
I'm not sure if this is correct way, but it worked when recovering from corepack disable npm.
I am using nvm.
$ corepack disable npm
$ npm -v
zsh: command not found: npm
# recovery
$ corepack enable npm # shims : $NVM_BIN/npm -> ../lib/node_modules/corepack/dist/npm.js
$ npm i -g npm # shims : $NVM_BIN/npm -> ../lib/node_modules/npm/bin/npm-cli.js
Also, ls -l $NVM_BIN command can tell us what package manager managed by corepack.
I'm more interested by the reasons why you'd call
disable?
I call corepack disable as it does not support http_proxy and so yarn is not getting found, for example.