Files
market/skills/docx/scripts/with-deps.py
Zxy-y 17fe79ab49 feat(docx): 跨平台启动器替换 bash 包装,复用预装依赖免每次安装 (#21)
## 概述 / Summary

把 docx 技能对"客户端预装运行时依赖"的复用方式从 **bash 包装脚本**改为**跨平台 runtime 启动器**,实现
Win/macOS/Linux 一致、不依赖 Git Bash,并修复若干 POSIX 硬编码导致的 Windows 崩溃点。

Switch the docx skill's reuse of client-preinstalled runtime deps from a
**bash wrapper** to **cross-platform runtime launchers**, so it behaves
identically on Win/macOS/Linux without Git Bash, and fix several
POSIX-hardcoded crashes on Windows.

## 改动 / Changes

- **新增 / Add** `scripts/preload-deps.cjs`(Node 预加载,注入 `NODE_PATH`)与
`scripts/with-deps.py`(Python 启动器,按需切换到内置含 lxml 的 Python);**删除** bash 版
`with-deps.sh`。
- 生成走 `node -r preload-deps.cjs`,office 脚本走 `python with-deps.py` ——
离线复用预装的 docx-js / defusedxml / lxml,免每次 `npm`/`pip install`,且**不依赖
bash**。
- `comment.py` 补 defusedxml sys.path shim;`validate.py` 修临时目录泄漏(atexit
清理)。
- `accept_changes.py` 去除 `/tmp` 硬编码(`tempfile.gettempdir` +
`Path.as_uri`);`soffice.py` 仅 Linux 启用 AF_UNIX shim,避免 Windows 崩溃。
- `SKILL.md` / `SKILL.zh-CN.md` 同步命令形式、加 ESM
警告与外部工具(pandoc/LibreOffice/poppler)跨平台安装指引,`source_hash` 重算。

## 测试 / Testing

- 真实 dev 根目录端到端:生成 docx(免安装)+ 完整 XSD 校验(含 lxml)+ unpack/pack 往返均通过。
- 仓库 `validate-i18n.py` 校验通过;全 py 脚本 `py_compile` + `preload-deps.cjs`
`node --check` 通过。

---

- [x] 我已阅读并同意 CLA / I have read and agree to the CLA

Co-authored-by: 张馨元 <zhangxy@iynss.com>
Co-authored-by: Yige <a@wyr.me>
2026-06-04 11:14:36 +08:00

75 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""with-deps.py —— 跨平台 Python 启动器(无需 bash对应已废弃的 with-deps.sh
让 office 脚本复用客户端预装的共享依赖,免去运行时 pip install
- defusedxml纯 Python注入 PYTHONPATH<ROOT>/runtime-deps/python-libs
- lxml编译型扩展绑定具体解释器 → 若存在受控 Python<ROOT>/runtime-deps/
python-runtime已装 lxml用它运行目标脚本从而离线启用完整 XSD 校验;
受控 Python 不存在 / 无法执行(如 macOS 公证拦截)→ 自动退回当前 Python
(此时 lxml 缺失,校验会优雅降级跳过,不会崩)。
纯 Python 实现,在 Windows / macOS / Linux 上用同一条命令运行,不依赖 bash
python "<skill-dir>/scripts/with-deps.py" office/unpack.py document.docx unpacked/
python "<skill-dir>/scripts/with-deps.py" office/validate.py doc.docx
目标脚本以 [解释器, 目标, *参数] 直接拉起 —— 等价于 `python <目标>`,因此脚本目录
会被 Python 自动加入 sys.path、__name__ == "__main__"、argv 与直接运行完全一致。
"""
import os
import subprocess
import sys
_HERE = os.path.dirname(os.path.abspath(__file__)) # .../skills/docx/scripts
# scripts → docx → skills → <ROOT>
_ROOT = os.path.abspath(os.path.join(_HERE, "..", "..", ".."))
_DEPS = os.path.join(_ROOT, "runtime-deps")
_PYLIBS = os.path.join(_DEPS, "python-libs")
_BUNDLED = os.path.join(
_DEPS,
"python-runtime",
"python.exe" if os.name == "nt" else os.path.join("bin", "python3"),
)
def main() -> int:
if len(sys.argv) < 2:
sys.stderr.write("usage: with-deps.py <script.py> [args...]\n")
return 2
# 目标脚本相对 scripts/ 解析(如 office/validate.py也支持绝对路径
arg = sys.argv[1]
target = arg if os.path.isabs(arg) else os.path.join(_HERE, arg)
if not os.path.isfile(target):
sys.stderr.write(f"with-deps.py: target not found: {target}\n")
return 2
# 选解释器:有受控 Python含 lxml且当前不是它 → 用它;否则用当前/系统 Python
interp = sys.executable
if os.path.isfile(_BUNDLED) and os.path.realpath(_BUNDLED) != os.path.realpath(sys.executable):
interp = _BUNDLED
# 注入 defusedxmlos.pathsep 跨平台自动 ';' / ':'
env = dict(os.environ)
if os.path.isdir(_PYLIBS):
existing = env.get("PYTHONPATH")
env["PYTHONPATH"] = _PYLIBS + (os.pathsep + existing if existing else "")
cmd = [interp, target, *sys.argv[2:]]
try:
rc = subprocess.run(cmd, env=env).returncode
except OSError:
rc = 126 # 受控 Python 无法启动
# 受控 Python 跑不了rc<0=被信号杀,如 macOS Gatekeeper126/127=无法执行)
# → 退回系统 Python让脚本在缺 lxml 时优雅降级,而不是把整条命令判为失败
if interp != sys.executable and (rc < 0 or rc in (126, 127)):
rc = subprocess.run([sys.executable, target, *sys.argv[2:]], env=env).returncode
return rc
if __name__ == "__main__":
raise SystemExit(main())