npm ERR! cb() never called 修复:npm 与 Node.js 版本不兼容的根因
npm ERR! cb() never called! 看起来像 npm 内部崩溃,但真正的根因通常是 npm 版本与当前 Node.js 版本不兼容。 最常见根因:版本不匹配 错误表现: ERROR: npm v11.17.0 is known not to run on Node.js v14.21.3. This version of npm supports the following node versions: `^20.17.0 || >=22.9.0`.SyntaxError: Unexpected token '&&=' at wrapSafe (internal/modules/cjs/loader.js:1029:16)npm 11 内部使用了 &&= 逻辑赋值运算符,这是 ES2021 特性,Node.js 14 不支持,导致 npm 自身启动时语法解析失败。 解决方案:切换 Node.js 版本 # 查看当前版本 node -v npm -v# 用 nvm 切换到兼容版本 nvm install 22 nvm use 22# 验证 node -v # v22.x.x npm -v # 10.x.x 或更高通用修复流程 如果版本兼容但仍然报错: 1. 升级 npm npm install -g npm@latest2. 清理缓存重装 npm cache clean --force rm -rf node_modules rm package-lock.json npm installmacOS 还可以删除整个 npm 缓存目录: rm -rf ~/.npm3. 切换国内镜像 国内网络拉包失败时: npm config set registry https://registry.npmmirror.com npm install4. 老项目依赖冲突 npm install --legacy-peer-depsnpm 与 Node.js 版本对照npm 版本 最低 Node.js 要求npm 11 Node.js 20.17 / 22.9+npm 10 Node.js 18+npm 9 Node.js 14.17+npm 8 Node.js 12+用 nvm 安装 LTS 版本可以拿到对应版本的 npm: nvm install --lts nvm use --lts
cp 报错 cannot overwrite non-directory:目录被同名文件占位
某次要把 pojie/ 目录复制进 wpa-dictionary/ 里,cp 一直报错: $ cp -r pojie/ wpa-dictionary/pojie cp: cannot overwrite non-directory 'wpa-dictionary/pojie' with directory 'pojie/'$ cp -r pojie wpa-dictionary/ cp: cannot overwrite non-directory 'wpa-dictionary/pojie' with directory 'pojie'$ cp -f pojie wpa-dictionary/ cp: -r not specified; omitting directory 'pojie'问题原因 这个报错不是 cp 参数写错了,而是 目标路径 wpa-dictionary/pojie 已经存在一个普通文件,cp 不允许用目录去覆盖一个非目录。 先用 ls -l 确认: $ ls -l wpa-dictionary/pojie -rw-r--r-- 1 root root 1234 Jun 15 10:00 wpa-dictionary/pojie看到开头是 - 说明它是普通文件。而你想执行的 cp -r pojie wpa-dictionary/ 实际上等于 cp -r pojie wpa-dictionary/pojie——目标名字已经被文件占了。 三种处理方式 方案一:删除同名文件 如果目标位置那个文件本来就没用,直接删掉再复制: rm -f wpa-dictionary/pojie cp -r pojie wpa-dictionary/方案二:换个目标名 不想删旧文件的话,改个新名字: cp -r pojie wpa-dictionary/pojie_new方案三:先看清目录结构 用 tree 看一眼当前目录状态,避免以后再踩: tree -L 2 .或者两条 ls -ld 直接对比属性: ls -ld pojie ls -ld wpa-dictionary/pojie看到一个是 d 开头(目录)、一个是 - 开头(文件),就知道冲突在哪里了。 一句话总结 cp: cannot overwrite non-directory = 目标位置已被同名普通文件占位。要么删掉旧文件,要么换个目标名。
Linux cp 报错 cannot overwrite non-directory with directory:原因和解决方法
cp -r pojie/ wpa-dictionary/pojie # cp: cannot overwrite non-directory 'wpa-dictionary/pojie' with directory 'pojie/'这个错误不是 cp 参数写错,而是目标路径 wpa-dictionary/pojie 已经存在一个普通文件,不是目录。 诊断 ls -ld wpa-dictionary/pojie如果输出以 - 开头,说明是普通文件: -rw-r--r-- 1 root root 1234 Jun 15 wpa-dictionary/pojie如果是目录会以 d 开头: drwxr-xr-x 2 root root 4096 Jun 15 wpa-dictionary/pojie解决方案 方案 1:删除目标文件后再复制 rm -f wpa-dictionary/pojie cp -r pojie wpa-dictionary/方案 2:复制到不同名称 cp -r pojie wpa-dictionary/pojie_backup方案 3:先移动目标文件 mv wpa-dictionary/pojie wpa-dictionary/pojie.bak cp -r pojie wpa-dictionary/cp 目录行为说明 cp -r src dst 的行为取决于 dst 是否存在:目标是否存在 行为dst 不存在 把 src 复制为 dst(改名)dst 是目录 把 src 整体放进 dst/srcdst 是普通文件 报错:cannot overwrite non-directory例如: # 目标目录 backup/ 不存在 cp -r project/ backup/ # 结果:backup/ 就是 project/ 的副本# 目标目录 backup/ 已存在 cp -r project/ backup/ # 结果:backup/project/(嵌套了一层)注意末尾斜杠的影响:cp -r pojie/ dst 和 cp -r pojie dst 行为可能不同(前者复制内容,后者复制整个目录)。 rsync 作为替代 目录同步推荐用 rsync,语义更清晰,且支持增量更新: # 把 pojie/ 的内容同步到 wpa-dictionary/pojie/ rsync -av pojie/ wpa-dictionary/pojie/# 只复制新文件,跳过已存在的 rsync -av --ignore-existing pojie/ wpa-dictionary/pojie/# 删除目标中源没有的文件(镜像同步) rsync -av --delete pojie/ wpa-dictionary/pojie/rsync 在处理已存在目标时不会报上面的错误,会直接合并或覆盖。
看 free -h 别只盯 free 那列:available 才是真
服务器一查内存: $ free -h total used free shared buff/cache available Mem: 14Gi 10Gi 146Mi 0.0Ki 4.2Gi 4.1Gi Swap: 0B 0B 0B看到 free: 146Mi 就觉得"内存要爆了"——不对,Linux 从来不闲着,它会把空闲内存拿去做各种缓存。 各列到底是什么列 含义total 总内存used 已被进程占用free 完全空闲,谁都没在用(少不代表不好)shared tmpfs / shared memory 占用buff/cache 内核用作 page cache / dentry 缓存available 实际还能分配给程序的内存(这个才是真)关键:buff/cache 是弹性的——一旦有程序需要内存,内核会立刻回收 cache 让出来。所以真正衡量"还剩多少"的指标是 available,不是 free。 上面例子里 available = 4.1Gi,说明还能舒服再开 4G 内存的程序,没问题。 buff/cache 具体缓存什么Page cache:读过的文件内容缓存在内存里,下次读同一文件秒回 Dentry cache:目录项缓存,让 ls / find 之类操作更快 Inode cache:文件元数据(大小、时间戳)缓存 Buffers:块设备 IO 缓冲这些都是性能加速——把内存"用起来"总比闲着好。 手动清 cache 极少用得上,但知道一下: sync # 先把脏页刷盘 echo 3 > /proc/sys/vm/drop_caches # 清所有 cache1 = 清 page cache 2 = 清 dentry + inode 3 = 全清清完 free -h 里 free 会瞬间变大——但系统 IO 性能会立刻下降,因为所有文件读要重新从磁盘拉。生产上除非做基准测试,不然不要清。 Swap = 0 的风险 上面的例子 Swap: 0B。开 swap 有一个好处:内存暂时不够时,把不活跃页面换到磁盘,避免 OOM Killer 杀进程。 没 swap 的情况,某个进程突然吃到 15G 内存,系统会直接触发 OOM Killer: Killed process 12345 (python) Killed process 23456 (mysqld)杀谁看 oom_score(内核给每个进程算一个"该杀分数"),基本是"占内存多、优先级低"的先中招。 加个 swap(4G) sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile验证: free -h # Swap: 4.0Gi ...开机自动挂载: echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstabSwap 大小经验:内存 < 2G:swap 是内存的 2 倍 内存 2-8G:swap 等于内存 内存 > 8G:swap 2-4G 或者干脆不开(数据库服务器)找谁在吃内存 RSS 排序(真实占用物理内存): ps aux --sort=-rss | head -20或者 top 里按 M 排。列 RES才是实际占用,VIRT(虚拟内存)意义不大。 看某个进程详情: cat /proc/<PID>/status | grep -E 'VmRSS|VmSize'看具体是哪部分: pmap -x <PID> | tail -1MySQL / Redis / Java 类"吃内存大户" 这些程序的内存占用大多是配置定的:MySQL:innodb_buffer_pool_size,默认可能占 70% 内存 Redis:maxmemory,不配就吃到爆 Java:-Xmx 设堆大小,不设 JVM 会按机器算上生产前把这些参数对着物理内存卡好,不然多进程叠加轻松超总内存。 Linux OOM 前的预警 看 dmesg 有没有: sudo dmesg -T | grep -i "oom\|killed"Out of memory: Killed process 12345 (python) total-vm:15234567kB看到这个说明已经杀过了。生产上应该主动监控——node_exporter + Prometheus 采 node_memory_MemAvailable_bytes,跌破阈值发告警。 一句话总结 free -h 看的是 available,不是 free;buff/cache 是好东西不是问题。没 swap 的服务器建议加 4G 兜底,主要吃内存的程序(MySQL / Redis / Java)参数要卡死。
Konva 做标注工具:Stage 缩放平移的正确姿势
Konva 是我做图片标注类工具时首选的 canvas 库。做缩放平移功能时,一个常犯的错——去缩放图片节点本身——会导致标注框和图片坐标错位。正确的做法是缩放整个 Stage。 缩放 Image 还是缩放 Stage 先看差别: // ❌ 缩放 Image 节点 imageNode.scale({ x: 2, y: 2 }); // 只有图片变大,标注框不动// ✅ 缩放整个 Stage stage.scale({ x: 2, y: 2 }); // 图片和所有标注框一起变大,坐标对齐CVAT、LabelStudio、AnyLabeling 这类专业标注工具全走 Stage 缩放路线。 鼠标位置为中心的滚轮缩放 图片查看器的标配——滚轮向前放大、向后缩小、以鼠标所在位置为中心。核心逻辑: stage.on("wheel", e => { e.evt.preventDefault(); const scaleBy = 1.05; const oldScale = stage.scaleX(); const pointer = stage.getPointerPosition(); // 缩放前鼠标在"世界坐标"里对应的点 const mousePointTo = { x: (pointer.x - stage.x()) / oldScale, y: (pointer.y - stage.y()) / oldScale, }; const newScale = e.evt.deltaY > 0 ? oldScale / scaleBy : oldScale * scaleBy; stage.scale({ x: newScale, y: newScale }); // 保持鼠标下的那个点不移动 stage.position({ x: pointer.x - mousePointTo.x * newScale, y: pointer.y - mousePointTo.y * newScale, }); stage.batchDraw(); });关键在这两步——先记录鼠标下的世界坐标,缩放后把 stage.position 反算回来,让那个点仍然停在鼠标下。这个技巧不止 Konva,任何 canvas 缩放都能套。 平移(拖拽画布) 按住空格或者中键拖动画布: stage.draggable(true); // 全局启用拖拽或者只在按住空格时拖: window.addEventListener("keydown", e => { if (e.code === "Space") stage.draggable(true); }); window.addEventListener("keyup", e => { if (e.code === "Space") stage.draggable(false); });缩放限制 别让用户无脑放大 100 倍或缩小成一个点: const newScale = Math.max(0.1, Math.min(oldScale * factor, 20));屏幕坐标 → 原图坐标 标注框保存到后端时,得存原图坐标,不能存屏幕坐标(用户下次打开缩放级别不一样就错了): function stagePointToImage(pos) { return { x: (pos.x - stage.x()) / stage.scaleX(), y: (pos.y - stage.y()) / stage.scaleY(), }; }// 用法 const pos = stage.getPointerPosition(); const realPos = stagePointToImage(pos);反过来"原图坐标 → 屏幕位置"用于展示已有标注: function imageToStagePoint(p) { return { x: p.x * stage.scaleX() + stage.x(), y: p.y * stage.scaleY() + stage.y(), }; }推荐的图层结构 标注类项目一般这样组织: Stage └─ Layer ├─ Image (背景图) ├─ Group (标注) │ ├─ Rect │ ├─ Rect │ └─ Rect └─ Group (UI) (十字线、悬浮提示等)Layer 里可以再分组,方便批量控制显隐(比如"隐藏所有标注只看图"): annotationsGroup.visible(false);一句话总结 Konva 做标注工具:缩放 Stage 而不是 Image 节点、鼠标为中心的缩放靠"世界坐标反算 position"、坐标始终以原图为基准存储。三件事做对,标注永远不错位。
