2026-09-15 21:17:12 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""find-ui-scan.py —— 在**服务器上**扫描「哪些包注册了实例 UI 分区」,输出 TSV。
|
|
|
|
|
|
|
|
|
|
|
|
由 `scripts/find-ui.cjs` 通过 `ssh <host> python3 -` 喂进来执行(本文件不在服务器上落地)。
|
|
|
|
|
|
为什么要落到服务器上跑:**权威事实是"活着的 profile 里装了什么"**,不是仓里的快照、
|
|
|
|
|
|
也不是文档库里的历史副本(2026-09-15 就是在这上面绕了很久)。
|
|
|
|
|
|
|
|
|
|
|
|
输出 TSV(每行一个分区):
|
|
|
|
|
|
pkg <TAB> origin <TAB> file <TAB> id <TAB> order <TAB> label_raw
|
|
|
|
|
|
|
|
|
|
|
|
scan 顺序:**自研(@dsh-local)在前**,官方在后 —— 排查时先看自己的。
|
|
|
|
|
|
"""
|
|
|
|
|
|
import glob
|
|
|
|
|
|
import json
|
2026-09-19 15:42:56 +08:00
|
|
|
|
import os
|
2026-09-15 21:17:12 +08:00
|
|
|
|
import re
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
2026-09-19 15:42:56 +08:00
|
|
|
|
# 用户根目录由调用方(`scripts/find-ui.mjs`)通过 `DSHS_USERS_DIR` 传入 —— 它从 `config/` 读,
|
|
|
|
|
|
# 不在这里写死服务器布局。⛔ 缺失时**显式失败**,绝不回落到某个猜测值(静默 0 命中 = 假阴性)。
|
|
|
|
|
|
USERS_DIR = os.environ.get('DSHS_USERS_DIR', '').rstrip('/')
|
|
|
|
|
|
if not USERS_DIR:
|
|
|
|
|
|
sys.stderr.write('!! 未收到 DSHS_USERS_DIR(用户数据根)—— 由 scripts/find-ui.mjs 注入,勿手动直连\n')
|
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
|
|
|
|
|
|
|
PROFILE_GLOB = USERS_DIR + '/*/home/profiles/web/node_modules/@dsh-local/*/lib/client.js'
|
2026-09-15 21:17:12 +08:00
|
|
|
|
OFFICIAL_GLOB = '/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/*/lib/client.js'
|
|
|
|
|
|
|
|
|
|
|
|
# 注册对象形如: { name: "settings.section", id: "x", order: 100, label: … }
|
|
|
|
|
|
REG = re.compile(r'name:\s*"settings\.section"')
|
|
|
|
|
|
ID = re.compile(r'\bid:\s*"([^"]+)"')
|
|
|
|
|
|
ORDER = re.compile(r'\border:\s*(-?\d+)')
|
|
|
|
|
|
LABEL_LIT = re.compile(r'label:\s*(?:\([^)]*\)\s*=>\s*|function\s*\([^)]*\)\s*\{\s*return\s*)\s*"((?:[^"\\]|\\.)*)"')
|
|
|
|
|
|
LABEL_KEY = re.compile(r'label:\s*(?:\([^)]*\)\s*=>\s*|function\s*\([^)]*\)\s*\{\s*return\s*)\s*t\(\s*"([^"]+)"\s*\)')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unique_files(pattern):
|
|
|
|
|
|
"""同一 profile 内容一致 ⇒ 只取**第一个** profile,避免同一分区重复几十行。"""
|
|
|
|
|
|
hits = sorted(glob.glob(pattern))
|
|
|
|
|
|
if not hits:
|
|
|
|
|
|
return []
|
|
|
|
|
|
if 'users' in pattern:
|
|
|
|
|
|
# 取第一个用户目录下的全部 @dsh-local 包
|
|
|
|
|
|
first_user = hits[0].split('/home/profiles/')[0]
|
|
|
|
|
|
return [h for h in hits if h.startswith(first_user + '/')]
|
|
|
|
|
|
return hits
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def label_of(text, raw):
|
|
|
|
|
|
"""label 是字面量 ⇒ 解码转义;是 t("key") ⇒ 在同文件的词典里找值。"""
|
|
|
|
|
|
m = LABEL_LIT.search(raw)
|
|
|
|
|
|
if m:
|
|
|
|
|
|
lit = m.group(1)
|
|
|
|
|
|
# 关键:`\u7528\u6237\u8bbe\u7f6e` 这类**转义 unicode** 要还原成人能看的字
|
|
|
|
|
|
try:
|
|
|
|
|
|
return json.loads('"' + lit + '"')
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return lit
|
|
|
|
|
|
m = LABEL_KEY.search(raw)
|
|
|
|
|
|
if m:
|
|
|
|
|
|
key = m.group(1)
|
|
|
|
|
|
d = re.search(r'"' + re.escape(key) + r'":\s*"((?:[^"\\]|\\.)*)"', text)
|
|
|
|
|
|
if d:
|
|
|
|
|
|
try:
|
|
|
|
|
|
return json.loads('"' + d.group(1) + '"')
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return d.group(1)
|
|
|
|
|
|
return 't("%s")' % key
|
|
|
|
|
|
return '(无 label)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def scan(files, origin):
|
|
|
|
|
|
for f in files:
|
|
|
|
|
|
try:
|
|
|
|
|
|
text = open(f, encoding='utf-8', errors='replace').read()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if 'settings.section' not in text:
|
|
|
|
|
|
continue
|
|
|
|
|
|
# ⚠️ 用 removeprefix 而不是 lstrip:lstrip 是按**字符集**删,会把官方包的 "@deepseek" 也啃掉
|
|
|
|
|
|
k = f.split('/node_modules/')[-1].split('/lib/client.js')[0]
|
|
|
|
|
|
pkg = k.removeprefix('@dsh-local/')
|
|
|
|
|
|
for m in REG.finditer(text):
|
|
|
|
|
|
raw = text[m.start():m.start() + 420]
|
|
|
|
|
|
i = ID.search(raw)
|
|
|
|
|
|
if not i:
|
|
|
|
|
|
continue
|
|
|
|
|
|
o = ORDER.search(raw)
|
|
|
|
|
|
print('\t'.join([
|
|
|
|
|
|
pkg, origin, f, i.group(1), o.group(1) if o else '—', label_of(text, raw),
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
mine = unique_files(PROFILE_GLOB)
|
|
|
|
|
|
if not mine:
|
|
|
|
|
|
sys.stderr.write('!! 没找到任何 profile 的 @dsh-local 包(集群模式下用户可能落别的 Worker)\n')
|
|
|
|
|
|
scan(mine, '自研')
|
|
|
|
|
|
scan(unique_files(OFFICIAL_GLOB), '官方')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
main()
|