# -*- coding: utf-8 -*- """ 补缺脚本:扫描 D:\dsh-install 下所有已装包的 dependencies, 把缺失的包(新版 dsh 新增的依赖)从 npmmirror 下载补上。 用法: python -S fix_deps.py [版本号] """ import urllib.request, tarfile, io, json, os, shutil, sys, glob # 自动检测 D:\dsh-install 下最新部署目录(避免硬编码 hash,换机器/升级通用) _install_dirs = sorted(glob.glob(r'D:\dsh-install\*'), key=os.path.getmtime, reverse=True) if _install_dirs: NM = os.path.join(_install_dirs[0], 'node_modules') else: print('错误: D:\dsh-install 下无部署目录,先跑 dsh_upgrade.py'); sys.exit(1) REG = 'https://registry.npmmirror.com' VERSION = sys.argv[1] if len(sys.argv) > 1 else os.environ.get('DSH_UPGRADE_VERSION', '0.1.1-rc.2') def log(m): print(m, flush=True) def get(url, timeout=60): req = urllib.request.Request(url, headers={'User-Agent': 'python-fix-deps'}) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read() def collect_pkg_map(): """遍历所有已装包,返回 {包名: package.json 路径}""" pmap = {} for n in os.listdir(NM): p = os.path.join(NM, n, 'package.json') if n.startswith('@'): d = os.path.join(NM, n) if os.path.isdir(d): for s in os.listdir(d): q = os.path.join(d, s, 'package.json') if os.path.exists(q): pmap[f'{n}/{s}'] = q elif os.path.exists(p): pmap[n] = p return pmap def resolve_version(full_name): if full_name.startswith('@deepseek-ai/'): return VERSION try: d = json.loads(get(f'{REG}/{full_name}')) return d['dist-tags']['latest'] except Exception: return None def install_pkg(full_name): ver = resolve_version(full_name) if not ver: log(f' SKIP {full_name}(版本解析失败)') return False esc = full_name.replace('/', '%2F') pkg_short = full_name.split('/')[-1] url = f'{REG}/{esc}/-/{pkg_short}-{ver}.tgz' try: data = get(url) except Exception as e: log(f' FAIL {full_name}@{ver}: {e}') return False target = os.path.join(NM, *full_name.split('/')) if os.path.exists(target): shutil.rmtree(target) os.makedirs(target, exist_ok=True) tf = tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') for m in tf.getmembers(): if m.isdir() or not m.name.startswith('package/'): continue rel = m.name[len('package/'):] dest = os.path.join(target, rel) os.makedirs(os.path.dirname(dest), exist_ok=True) src = tf.extractfile(m) if src: with open(dest, 'wb') as f: f.write(src.read()) tf.close() log(f' INSTALL {full_name}@{ver}') return True def main(): missing = {} for round_no in range(1, 9): pmap = collect_pkg_map() have = set(pmap.keys()) missing = {} for name, pj in pmap.items(): try: with open(pj, encoding='utf-8') as f: data = json.load(f) except Exception: continue for dep_key in ('dependencies', 'optionalDependencies'): for dep, spec in (data.get(dep_key) or {}).items(): if dep not in have: missing[dep] = spec if not missing: log(f'第 {round_no} 轮: 无缺失,依赖完整') break log(f'第 {round_no} 轮: 缺 {len(missing)} 个依赖') ok = sum(1 for dep in sorted(missing) if install_pkg(dep)) if ok == 0: log('本轮 0 成功,停止') break have = collect_pkg_map() final = sorted(d for d in missing if d not in have) log('最终缺失: ' + (', '.join(final) if final else '无')) log('DONE') if __name__ == '__main__': main()