复制pnpm创建的node_modules文件夹到另一项目

分析内容

众所周知,npm下载的node_modules文件夹是可以直接复制剪切的,但是pnpm下载的不行

本文写了一个脚本处理了这个问题

事先声明,代码执行要好几分钟,效率上不如直接pnpm i拉取(执行很快)

该文件夹根内容大概内容分三类:

①.bin文件夹是很多脚本文件,作用不明,含有pnpm i时下载的路径,但不影响运行

②.pnpm文件夹包含所有module,里面也包括了少数的目录联接

③·根目录的其它几十个文件夹(长得像快捷方式)都是目录联接

所有的目录联接都指向了.pnpm里面的某些文件夹

Windows 中的硬链接、目录联接(软链接)、符号链接、快捷方式-阿里云开发者社区

        这些目录联接A指向了另一个目录B,A内部的元素和B是同一份,但点进A文件夹并不会像快捷方式一样跳转到B,A是B的别名。且A不能被剪切和复制(这样做只能获得一个普通文件夹A',复制时A'是空的,剪切时不会删除A,但会把B的全部内容剪切到A')

        可以通过命令行fsutil reparsepoint query "path" 或 python的win32file.GetFileAttributes(path) 判断一个目录是否是目录联接,后者因为不是命令,在python执行更快

        可以通过mklink /J "new_dir" "target"来创建指向target的目录联接

因此,流程为:

1、把该文件夹从A项目复制到B项目

2、遍历A项目的文件夹,找出所有联接目录,记录若干组位置和指向位置

3、把每一组位置和指向位置在/node_modules之前的部分替换为B项目所在的位置,删除B中复制失败的空文件夹,并在相同位置创建新的目录联接

代码:

其实还有很多bug,但是能满足要求,我不想改了:

1、运行结束后,会再打开界面重新运行一遍程序,可以ctrl+c取消

2、莫名其妙出现很多重复的操作,都会在第二次执行到时提示出错

3、其实创建联接(/J)不需要管理员权限,但是创建符号链接(/D)需要,所以创建管理员cmd那部分可以删掉。fsutil reparsepoint query输出符号链接的指向时,输出内容处理起来有点麻烦

本文是把C:\Users\HP\Desktop\web\demo-vue-master\node_modules复制到C:\Users\HP\Desktop\web\test\demo-vue-master\node_modules,具体替换内容可修改

import subprocess
import ctypes, sys
import os
import time
import win32file

def check_reparse_point(path):
    # 检查路径是否是联结点
    if win32file.GetFileAttributes(path) != 1040:   
        return None
    command = ['fsutil', 'reparsepoint', 'query', path]
    output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    if output.returncode != 0:
        print(f"命令执行出错:{output.stderr.decode('GBK').strip()}")
        return None
    else:
        lines = output.stdout.decode('GBK').strip().splitlines()
        location = lines[9].rsplit(' ', 1)[1]
        return location
        
def find_reparse_point(path):
    global total_num
    global temp
    reparse_points = []
    reparse_points_target = []
    for file in os.scandir(path):
        if os.path.isdir(file):
            check = check_reparse_point(file.path)
            if check != None:
                reparse_points.append(file.path)
                reparse_points_target.append(check)
                total_num += 1
            else :
                point,target = find_reparse_point(file)
                reparse_points += point
                reparse_points_target += target
        if total_num % 50 == 0:
            if total_num > temp:
                temp = total_num
                print(total_num,path)
    return reparse_points,reparse_points_target

def execute_command(command):
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,shell=True)
    output,error=process.communicate()
    if process.returncode!= 0:
        print(f"命令执行出错:{error.decode('GBK').strip()}")
        return None
    return output.decode('GBK').strip()

def generate_link(link,target):
    if os.path.exists(link):
        if not os.listdir(link):
            try:
                os.rmdir(link)
            except OSError as e:
                print(f"删除文件夹 '{link}' 时出错:{e}")
        else:
            print(f"文件夹 '{link}' 存在且不为空,无法删除。")
            return
    if os.path.exists(target):
        # J是联结,D是符号
        batch_command = ['mklink','/J', link, target]
        result = execute_command(batch_command)
        if result is not None:
            # print(f"{result}")
            pass
        else:
            print("链接创建失败。")
    else:
        print("目标文件夹",target,"不存在。")

def is_admin():
    try:
        if not ctypes.windll.shell32.IsUserAnAdmin():   # 如果不是管理员窗口,返回0
            # 以管理员权限重新运行程序
            ctypes.windll.shell32.ShellExecuteW(None,"runas", sys.executable, __file__, None, 1)
    except:
        print('error when check admin')
        exit(0)

def main():
    is_admin()
    global total_num
    source = R"C:\Users\HP\Desktop\web\demo-vue-master\node_modules"
    start_time = time.time()
    replace_pre = R"web\demo"
    replace_target = R"web\test\demo"

    try: 
        reparse_dirs,reparse_dirs_source = find_reparse_point(source)
        print(total_num)
        
        for i in range(len(reparse_dirs)):
            target_folder = reparse_dirs_source[i].replace(replace_pre, replace_target)
            linked_folder = reparse_dirs[i].replace(replace_pre, replace_target)
            generate_link(linked_folder,target_folder)
        
            
    except KeyboardInterrupt:
        print("发现的总数",total_num)
        print("耗时:",time.time()-start_time,"秒")


if __name__ == '__main__':
    total_num = 0 
    temp = 0    
    main()

附加:执行fsutil reparsepoint query的反馈

对于联接目录,输出为:

对于符号链接目录,输出为:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值