13
0
Fork 0
mirror of https://github.com/jdx/mise-action.git synced 2026-07-02 17:49:30 +00:00

fix: use cp instead of mv to install mise binary

io.mv uses fs.rename, which fails with EXDEV when the tool-cache
extraction temp dir and the mise bin dir are on different mounts
(as in Alpine containers). io.cp uses fs.copyFile, which works
across devices and preserves the source mode bits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Precioso 2026-06-17 15:54:10 +02:00
parent 2db43f9848
commit e5654ed8b5
3 changed files with 133 additions and 6 deletions

130
dist/index.js generated vendored
View file

@ -28779,9 +28779,31 @@ var __awaiter$j = (undefined && undefined.__awaiter) || function (thisArg, _argu
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const { chmod, copyFile, lstat, mkdir, open, readdir, rename, rm, rmdir, stat, symlink, unlink } = fs.promises;
const { chmod, copyFile: copyFile$1, lstat, mkdir, open, readdir, rename, rm, rmdir, stat, symlink, unlink } = fs.promises;
// export const {open} = 'fs'
const IS_WINDOWS$d = process.platform === 'win32';
/**
* Custom implementation of readlink to ensure Windows junctions
* maintain trailing backslash for backward compatibility with Node.js < 24
*
* In Node.js 20, Windows junctions (directory symlinks) always returned paths
* with trailing backslashes. Node.js 24 removed this behavior, which breaks
* code that relied on this format for path operations.
*
* This implementation restores the Node 20 behavior by adding a trailing
* backslash to all junction results on Windows.
*/
function readlink(fsPath) {
return __awaiter$j(this, void 0, void 0, function* () {
const result = yield fs.promises.readlink(fsPath);
// On Windows, restore Node 20 behavior: add trailing backslash to all results
// since junctions on Windows are always directory links
if (IS_WINDOWS$d && !result.endsWith('\\')) {
return `${result}\\`;
}
return result;
});
}
fs.constants.O_RDONLY;
function exists(fsPath) {
return __awaiter$j(this, void 0, void 0, function* () {
@ -28927,6 +28949,47 @@ var __awaiter$i = (undefined && undefined.__awaiter) || function (thisArg, _argu
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
/**
* Copies a file or folder.
* Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js
*
* @param source source path
* @param dest destination path
* @param options optional. See CopyOptions.
*/
function cp(source_1, dest_1) {
return __awaiter$i(this, arguments, void 0, function* (source, dest, options = {}) {
const { force, recursive, copySourceDirectory } = readCopyOptions(options);
const destStat = (yield exists(dest)) ? yield stat(dest) : null;
// Dest is an existing file, but not forcing
if (destStat && destStat.isFile() && !force) {
return;
}
// If dest is an existing directory, should copy inside.
const newDest = destStat && destStat.isDirectory() && copySourceDirectory
? path$1.join(dest, path$1.basename(source))
: dest;
if (!(yield exists(source))) {
throw new Error(`no such file or directory: ${source}`);
}
const sourceStat = yield stat(source);
if (sourceStat.isDirectory()) {
if (!recursive) {
throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`);
}
else {
yield cpDirRecursive(source, newDest, 0, force);
}
}
else {
if (path$1.relative(source, newDest) === '') {
// a file cannot be copied to itself
throw new Error(`'${newDest}' and '${source}' are the same file`);
}
yield copyFile(source, newDest, force);
}
});
}
/**
* Moves a path.
*
@ -29086,6 +29149,64 @@ function findInPath(tool) {
return matches;
});
}
function readCopyOptions(options) {
const force = options.force == null ? true : options.force;
const recursive = Boolean(options.recursive);
const copySourceDirectory = options.copySourceDirectory == null
? true
: Boolean(options.copySourceDirectory);
return { force, recursive, copySourceDirectory };
}
function cpDirRecursive(sourceDir, destDir, currentDepth, force) {
return __awaiter$i(this, void 0, void 0, function* () {
// Ensure there is not a run away recursive copy
if (currentDepth >= 255)
return;
currentDepth++;
yield mkdirP(destDir);
const files = yield readdir(sourceDir);
for (const fileName of files) {
const srcFile = `${sourceDir}/${fileName}`;
const destFile = `${destDir}/${fileName}`;
const srcFileStat = yield lstat(srcFile);
if (srcFileStat.isDirectory()) {
// Recurse
yield cpDirRecursive(srcFile, destFile, currentDepth, force);
}
else {
yield copyFile(srcFile, destFile, force);
}
}
// Change the mode for the newly created directory
yield chmod(destDir, (yield stat(sourceDir)).mode);
});
}
// Buffered file copy
function copyFile(srcFile, destFile, force) {
return __awaiter$i(this, void 0, void 0, function* () {
if ((yield lstat(srcFile)).isSymbolicLink()) {
// unlink/re-link it
try {
yield lstat(destFile);
yield unlink(destFile);
}
catch (e) {
// Try to override file permission
if (e.code === 'EPERM') {
yield chmod(destFile, '0666');
yield unlink(destFile);
}
// other errors = it doesn't exist, no work to do
}
// Copy over symlink
const symlinkFull = yield readlink(srcFile);
yield symlink(symlinkFull, destFile, IS_WINDOWS$d ? 'junction' : null);
}
else if (!(yield exists(destFile)) || force) {
yield copyFile$1(srcFile, destFile);
}
});
}
var __awaiter$h = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@ -89840,13 +89961,16 @@ async function setupMise(version, fetchFromGitHub = false) {
'--zstd',
'-x'
]);
await mv(path$1.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath);
// Use cp, not mv: the extraction temp dir and the mise
// bin dir can live on different mounts (e.g. inside Alpine
// containers), and rename across devices fails with EXDEV.
await cp(path$1.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath);
break;
}
case '.tar.gz': {
const archivePath = await downloadTool(url);
const extractDir = await extractTar(archivePath);
await mv(path$1.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath);
await cp(path$1.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath);
break;
}
default:

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View file

@ -349,13 +349,16 @@ async function setupMise(
'--zstd',
'-x'
])
await io.mv(path.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath)
// Use cp, not mv: the extraction temp dir and the mise
// bin dir can live on different mounts (e.g. inside Alpine
// containers), and rename across devices fails with EXDEV.
await io.cp(path.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath)
break
}
case '.tar.gz': {
const archivePath = await tc.downloadTool(url)
const extractDir = await tc.extractTar(archivePath)
await io.mv(path.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath)
await io.cp(path.join(extractDir, 'mise', 'bin', 'mise'), miseBinPath)
break
}
default: