UNPKG

@pika/pack

Version:
152 lines (151 loc) 6.01 kB
// // // This module serves as a wrapper for file operations that are inconsistant across node and OS versions. // import fs from 'fs'; // import {promisify} from './promise.js'; // import {constants} from './fs.js'; // export type CopyFileAction = { // src: string, // dest: string, // atime: Date, // mtime: Date, // mode: number, // }; // let disableTimestampCorrection: boolean; // OS dependent. will be detected on first file copy. // const readFileBuffer = promisify(fs.readFile); // const close: (fd: number) => Promise<void> = promisify(fs.close); // const lstat: (path: string) => Promise<fs.Stats> = promisify(fs.lstat); // const open: (path: string, flags: string | number, mode: number) => Promise<number> = promisify(fs.open); // const futimes: (fd: number, atime: number, mtime: number) => Promise<void> = promisify(fs.futimes); // const write: ( // fd: number, // buffer: Buffer, // offset?: number, // length?: number, // position?: number, // ) => Promise<void> = promisify(fs.write); // /** // * Unlinks the destination to force a recreation. This is needed on case-insensitive file systems // * to force the correct naming when the filename has changed only in character-casing. (Jest -> jest). // */ // export const copyFile = async function(data: CopyFileAction, cleanup: () => any): Promise<void> { // // $FlowFixMe: Flow doesn't currently support COPYFILE_FICLONE // const ficloneFlag = (constants as any).COPYFILE_FICLONE || 0; // try { // await unlink(data.dest); // await copyFilePoly(data.src, data.dest, ficloneFlag, data); // } finally { // if (cleanup) { // cleanup(); // } // } // }; // // Node 8.5.0 introduced `fs.copyFile` which is much faster, so use that when available. // // Otherwise we fall back to reading and writing files as buffers. // const copyFilePoly: (src: string, dest: string, flags: number, data: CopyFileAction) => Promise<void> = ( // src, // dest, // flags, // data, // ) => { // if (fs.copyFile) { // return new Promise((resolve, reject) => // fs.copyFile(src, dest, flags, err => { // if (err) { // reject(err); // } else { // fixTimes(undefined, dest, data).then(() => resolve()).catch(ex => reject(ex)); // } // }), // ); // } else { // return copyWithBuffer(src, dest, flags, data); // } // }; // const copyWithBuffer: (src: string, dest: string, flags: number, data: CopyFileAction) => Promise<void> = async ( // src, // dest, // flags, // data, // ) => { // // Use open -> write -> futimes -> close sequence to avoid opening the file twice: // // one with writeFile and one with utimes // const fd = await open(dest, 'w', data.mode); // try { // const buffer = await readFileBuffer(src); // await write(fd, buffer, 0, buffer.length); // await fixTimes(fd, dest, data); // } finally { // await close(fd); // } // }; // // We want to preserve file timestamps when copying a file, since pika uses them to decide if a file has // // changed compared to the cache. // // There are some OS specific cases here: // // * On linux, fs.copyFile does not preserve timestamps, but does on OSX and Win. // // * On windows, you must open a file with write permissions to call `fs.futimes`. // // * On OSX you can open with read permissions and still call `fs.futimes`. // async function fixTimes(fd: number | undefined, dest: string, data: CopyFileAction): Promise<void> { // const doOpen = fd === undefined; // let openfd: number = fd ? fd : -1; // if (disableTimestampCorrection === undefined) { // // if timestamps match already, no correction is needed. // // the need to correct timestamps varies based on OS and node versions. // const destStat = await lstat(dest); // disableTimestampCorrection = fileDatesEqual(destStat.mtime, data.mtime); // } // if (disableTimestampCorrection) { // return; // } // if (doOpen) { // try { // openfd = await open(dest, 'a', data.mode); // } catch (er) { // // file is likely read-only // try { // openfd = await open(dest, 'r', data.mode); // } catch (err) { // // We can't even open this file for reading. // return; // } // } // } // try { // if (openfd) { // await futimes(openfd, data.atime, data.mtime); // } // } catch (er) { // // If `futimes` throws an exception, we probably have a case of a read-only file on Windows. // // In this case we can just return. The incorrect timestamp will just cause that file to be recopied // // on subsequent installs, which will effect pika performance but not break anything. // } finally { // if (doOpen && openfd) { // await close(openfd); // } // } // } // // Compare file timestamps. // // Some versions of Node on windows zero the milliseconds when utime is used. // export const fileDatesEqual = (a: Date, b: Date) => { // const aTime = a.getTime(); // const bTime = b.getTime(); // if (process.platform !== 'win32') { // return aTime === bTime; // } // // See https://github.com/nodejs/node/pull/12607 // // Submillisecond times from stat and utimes are truncated on Windows, // // causing a file with mtime 8.0079998 and 8.0081144 to become 8.007 and 8.008 // // and making it impossible to update these files to their correct timestamps. // if (Math.abs(aTime - bTime) <= 1) { // return true; // } // const aTimeSec = Math.floor(aTime / 1000); // const bTimeSec = Math.floor(bTime / 1000); // // See https://github.com/nodejs/node/issues/2069 // // Some versions of Node on windows zero the milliseconds when utime is used // // So if any of the time has a milliseconds part of zero we suspect that the // // bug is present and compare only seconds. // if (aTime - aTimeSec * 1000 === 0 || bTime - bTimeSec * 1000 === 0) { // return aTimeSec === bTimeSec; // } // return aTime === bTime; // };