at master 94 lines 2.7 kB view raw
1#!/usr/bin/env node 2const fs = require("fs"); 3const path = require("path"); 4 5async function asyncFilter(arr, pred) { 6 const filtered = []; 7 for (const elem of arr) { 8 if (await pred(elem)) { 9 filtered.push(elem); 10 } 11 } 12 return filtered; 13} 14 15// Get a list of all _unmanaged_ files in node_modules. 16// This means every file in node_modules that is _not_ a symlink to the Nix store. 17async function getUnmanagedFiles(storePrefix, files) { 18 return await asyncFilter(files, async (file) => { 19 const filePath = path.join("node_modules", file); 20 21 // Is file a symlink 22 const stat = await fs.promises.lstat(filePath); 23 if (!stat.isSymbolicLink()) { 24 return true; 25 } 26 27 // Is file in the store 28 const linkTarget = await fs.promises.readlink(filePath); 29 return !linkTarget.startsWith(storePrefix); 30 }); 31} 32 33async function main() { 34 const args = process.argv.slice(2); 35 const storePrefix = args[0]; 36 const sourceModules = args[1]; 37 38 // Ensure node_modules exists 39 try { 40 await fs.promises.mkdir("node_modules"); 41 } catch (err) { 42 if (err.code !== "EEXIST") { 43 throw err; 44 } 45 } 46 47 const files = await fs.promises.readdir("node_modules"); 48 49 // Get deny list of files that we don't manage. 50 // We do manage nix store symlinks, but not other files. 51 // For example: If a .vite was present in both our 52 // source node_modules and the non-store node_modules we don't want to overwrite 53 // the non-store one. 54 const unmanaged = await getUnmanagedFiles(storePrefix, files); 55 const managed = new Set(files.filter((file) => ! unmanaged.includes(file))); 56 57 const sourceFiles = await fs.promises.readdir(sourceModules); 58 await Promise.all( 59 sourceFiles.map(async (file) => { 60 const sourcePath = path.join(sourceModules, file); 61 const targetPath = path.join("node_modules", file); 62 63 // Skip file if it's not a symlink to a store path 64 if (unmanaged.includes(file)) { 65 console.log(`'${targetPath}' exists, cowardly refusing to link.`); 66 return; 67 } 68 69 // Don't unlink this file, we just wrote it. 70 managed.delete(file); 71 72 // Link file 73 try { 74 await fs.promises.symlink(sourcePath, targetPath); 75 } catch (err) { 76 // If the target file already exists remove it and try again 77 if (err.code !== "EEXIST") { 78 throw err; 79 } 80 await fs.promises.unlink(targetPath); 81 await fs.promises.symlink(sourcePath, targetPath); 82 } 83 }) 84 ); 85 86 // Clean up store symlinks not included in this generation of node_modules 87 await Promise.all( 88 Array.from(managed).map((file) => 89 fs.promises.unlink(path.join("node_modules", file)), 90 ) 91 ); 92} 93 94main();