v2.3.0: info 新增开发包/传输请求字段 (纯只读 E071)

- info 输出新增「开发包」(packageRef)、「传输请求」(纯只读查 E071)

- 优雅降级: 无请求/查询失败/非独立传输对象, 不中断 info

- bump 2.2.1 -> 2.3.0

- pack 从源码同步 (含 parser/transport 最新代码)
This commit is contained in:
WuRangyu
2026-08-06 11:57:36 +08:00
parent 607445b338
commit 02bc2c5165
7 changed files with 381 additions and 193 deletions
+283 -189
View File
@@ -1,238 +1,332 @@
---
name: sap-cli
description: "SAP ABAP ADT CLI for AI agents. Trigger when: operating ABAP objects, syncing code to/from SAP, managing transports, querying DDIC, running ABAP programs, reading SAP tables, or any SAP development task via terminal."
description: "Use when you need to operate SAP ABAP development objects syncing code, querying metadata, managing transports, reading tables, or any task involving SAP ADT REST API. Trigger on: ABAP objects, SAP development, transport requests, DDIC types, SE11/SE16N/SE38/SE80 equivalents, 'sync to SAP', 'download from SAP', 'activate ABAP', 'read SAP table'. NOT for developing sap-cli itself."
version: "2.3.0"
author: WuRangyu
license: MIT
triggers:
- "ABAP development"
- "sync to SAP"
- "download from SAP"
- "SAP transport request"
- "DDIC/SE11/SE16N/SE38/SE80"
- "SAP ADT REST API"
---
# sap-cli — SAP ABAP Development Object Management
# sap-cli — SAP ABAP 开发对象管理工具
Python CLI wrapping the SAP ADT REST API. Manages the full lifecycle of ABAP development objects from the terminal. Core loop: **download → local edit → sync back to SAP**.
sap-cli 是一个 Python CLI,封装 SAP ADT REST API,让你在终端完成 ABAP 开发对象的完整生命周期管理。
## Quick Start
核心工作流:**download → 本地编辑 → sync 回 SAP**
---
## 首次使用:安装与配置
### 第 1 步:安装 sap-cli
如果 `python main.py --help` 无法执行,先运行安装脚本:
```bash
# 1. Single object: download → edit → sync
python <SKILL_DIR>/scripts/setup.py install
```
此脚本会:
1. 安装依赖(`requests`
2. 将 sap-cli 安装为可编辑的 Python 包
3. 创建配置文件模板
安装完成后验证:
```bash
python main.py --help
```
### 第 2 步:配置 SAP 连接
编辑配置文件 `assets/config.ini`(从 `config.ini.example` 复制):
```ini
[SAP]
host = http://your-sap-server:8000
client = 100
user = your_username
password = your_password
```
也可通过环境变量配置(优先级高于配置文件):
- `SAP_HOST` / `SAP_CLIENT` / `SAP_USER` / `SAP_PASSWORD`
### 第 3 步:验证连接
```bash
python main.py config show
python main.py transport list
```
---
## 调用方式
```bash
cd <SKILL_DIR>/assets && python main.py <command> [options]
```
全局参数(在命令之前):
| 参数 | 说明 |
|------|------|
| `--profile <name>` | 使用指定配置 profile |
| `--verify-ssl` | 启用 SSL 验证(默认关闭) |
---
## 命令速查
### CRUD(对象生命周期)
```bash
# 创建新对象
python main.py create --name ZMY_CLASS --type class --corr_nr DEVK901XXX
python main.py create --name ZMY_DOMAIN --type domain --definition domain.json --corr_nr DEVK901XXX
# 查询元数据
python main.py info --name ZMY_CLASS --type class
# 下载源码
python main.py download --name ZMY_CLASS --type class --path ./src
# edit ./src/zmy_class.abap
# 同步到 SAPlock → write → unlock → syntax check → activate
python main.py sync --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
# 2. Batch project sync (auto dependency-ordered)
python main.py init --path ./my_project # add objects under src/
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX
# 删除对象
python main.py delete --name ZMY_CLASS --type class
# 3. Table query: structure (SE11) + data (SE16N)
python main.py show-table --name ZMY_TABLE
python main.py read-table --name ZMY_TABLE --fields "F1,F2" --where "F1 = 'X'" --max-rows 50
# 单独激活(sap-cli 内置 NW 7.40 double-activate
python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX
# 单独上传(仅锁定→写入→解锁,不检查不激活)
python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
# 语法检查(检查远程对象,不上传不激活)
python main.py syntax-check --name ZMY_CLASS --type class
```
## Command Reference
**create 关键参数**
Global params (before command): `--profile <name>`, `--verify-ssl`. Optional params in `[ ]`.
| 参数 | 说明 |
|------|------|
| `--description` | 对象描述 |
| `--source <.abap>` | 自定义源码文件 |
| `--definition <.json>` | DDIC 定义文件(domain/dataelement/table/structure/tabletype |
| `--package <name>` | SAP 包(默认 `$TMP` |
| `--corr_nr` | 传输请求号 |
| Category | Command | Key Parameters |
|----------|---------|----------------|
| CRUD | `create` | `--name --type [--description --source <.abap> --definition <.json> --package <$TMP> --corr_nr]` |
| CRUD | `info` | `--name --type` [^1] |
| CRUD | `download` | `--name --type --path` |
| CRUD | `sync` | `--name --type --path --corr_nr` **(REQUIRED)** |
| CRUD | `delete` | `--name --type` |
| CRUD | `activate` | `--name --type --corr_nr` (built-in NW 7.40 double-activate) |
| CRUD | `upload` | `--name --type --path --corr_nr` (lock→write→unlock only; no check/activate) |
| CRUD | `syntax-check` | `--name --type` (remote object; no upload/activate) |
| Batch | `init` | `--path` |
| Batch | `refresh` | `--path` |
| Batch | `sync --all` | `--path --corr_nr [--dry-run]` |
| Batch | `analyze` | `--path` (dependency analysis) |
| Query | `show-table` | `--name` (SE11 structure) |
| Query | `read-table` | `--name [--fields --where --max-rows]` (SE16N data) |
| Query | `list` | `--type [--package --prefix]` |
| Query | `whereused` | `--name --type` |
| Query | `search` | `--query` |
| Query | `diff` | `--name --type --path` (local vs SAP) |
| Transport | `transport list` | — |
| Transport | `transport info` | `--corr_nr` |
| Transport | `transport release` | `--corr_nr` |
| Transport | `transport objects` | `--corr_nr` |
| Config | `config show` | — |
| Config | `config list-profiles` | — |
| Config | `config set` | `<key> <val>` |
| Config | `auth login` / `auth status` | password stored in keyring |
| Other | `run-program` | `--name` (SA38) |
| Other | `check` | `--name --type` (ATC) |
| Other | `format` | `--name --type` (Pretty Printer) |
| Other | `package create` | `--name` |
| Other | `cds download` | `--name --path` |
| Other | `scaffold` | `--name --template <t>` — alv-report, bapi-wrapper, interface-class, data-model |
### 批量操作
[^1]: ⚠️ `info` 不返回 `package`/`packageRef` 字段。需通过 ADT API 查询:`GET /sap/bc/adt/programs/programs/<name>` 返回 XML 中含 `<adtcore:packageRef>`。也可用 `list --package <pkg>` 反向查找。
Batch sync dependency order: `domain(10) → dataelement(20) → table(30) → tabletype(40) → interface(50) → class(60) → function(70) → report(80)`.
Step-wise sync for lock-heavy automation — split the atomic `sync` when lock conflicts are frequent; upload now, verify, activate later:
```bash
python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX # lock→write→unlock only
python main.py syntax-check --name ZDEPENDENT_REPORT --type report # verify dependents (no upload)
python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX # activate once clean
# 初始化项目结构
python main.py init --path ./my_project
# 刷新对象状态
python main.py refresh --path ./my_project
# 批量同步(自动按依赖拓扑排序)
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX --dry-run
```
### Supported Object Types (16)
依赖排序:`domain(10) → dataelement(20) → table(30) → tabletype(40) → interface(50) → class(60) → function(70) → report(80)`
| Category | Types | Has source |
|----------|-------|------------|
| Programs | `report`, `include` | ✅ |
### 数据查询
```bash
# 表结构(SE11 等价)
python main.py show-table --name ZMY_TABLE
# 表数据(SE16N 等价)
python main.py read-table --name ZMY_TABLE
python main.py read-table --name ZMY_TABLE --fields "FIELD1,FIELD2" --where "FIELD1 = 'X'" --max-rows 50
# 列出对象
python main.py list --type class --package ZSAPILOT
# Where-Used
python main.py whereused --name ZMY_CLASS --type class
# 源码搜索
python main.py search --query "CALL FUNCTION 'Z_MY_FUNC'"
```
### 远程执行
```bash
# 执行 ABAP 程序(SA38 等价)
python main.py run-program --name ZSAPILOT_SETUP
# 本地 vs SAP 差异
python main.py diff --name ZMY_CLASS --type class --path ./src/zmy_class.abap
```
### 传输请求
```bash
python main.py transport list
python main.py transport info --corr_nr DEVK901XXX
python main.py transport release --corr_nr DEVK901XXX
python main.py transport objects --corr_nr DEVK901XXX
```
### 配置与认证
```bash
python main.py config show
python main.py config list-profiles
python main.py config set host s4h.example.com
python main.py auth login # 密码保存到 keyring
python main.py auth status
```
### 其他
```bash
python main.py check --name ZMY_CLASS --type class # ATC 检查
python main.py format --name ZMY_CLASS --type class # Pretty Printer
python main.py package create --name ZSAPILOT # 创建包
python main.py cds download --name ZMY_CDS --path ./src # CDS View
python main.py analyze --path ./src # 依赖分析
python main.py scaffold --name ZMY_RPT --template alv-report # 项目模板
# 模板: alv-report, bapi-wrapper, interface-class, data-model
```
---
## 支持的对象类型(16 种)
| 分类 | 类型 | 有源码 |
|------|------|--------|
| 程序 | `report`, `include` | ✅ |
| OOP | `class`, `interface` | ✅ |
| Functions | `function` | ✅ |
| DDIC base | `domain`, `dataelement`, `table`, `structure` | ✅ |
| DDIC ext | `cdsview`, `view` | ✅ |
| DDIC no-source | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ |
| Groups | `functiongroup` | ❌ |
| 函数 | `function` | ✅ |
| DDIC 基础 | `domain`, `dataelement`, `table`, `structure` | ✅ |
| DDIC 扩展 | `cdsview`, `view` | ✅ |
| DDIC 无源码 | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ |
| 程序组 | `functiongroup` | ❌ |
**Function naming**: must use `group/module`, e.g. `ZMY_FGROUP/Z_MY_FUNC`.
**函数命名格式**:必须使用 `组名/模块名` 格式,如 `ZMY_FGROUP/Z_MY_FUNC`
## ⚠️ Constraints (HARD RULES — DO NOT VIOLATE)
---
Full rules: `references/sap-tool-constraints.md`. Violations are intercepted by hooks.
质检标准: `references/abap-coding-rules.md`(28条规则,6大类:安全/性能/可维护性/错误处理/NW740兼容/风格)。质检员在代码审计时以此文档为唯一标准,判定体系:✅合规 / ❌违反(必须修复) / ⚠️建议(推荐修复)。
## 常用工作流
1. **Only `python main.py <cmd>` touches SAP** — no `requests`/`urllib`, no curl to ADT/SOAP/RFC, no `python -c` inline, no importing `ADTClient` internals, no hand-built ADT XML. Exceptions: `run-program` custom reports, explicit user request, or developing sap-cli itself.
2. **Never modify SAP standard objects** (`CL_*`, `CX_*`, `IF_*`, `SAPL*`, or any non-user original system) — read-only only: download/info/list/search/whereused/show-table/read-table/diff. When unsure, ask the user.
3. **Never write system tables** (TADIR, E071, SEOCLASS, REPOSRC, D010SINF, T000, TDEVC, TSTC) — read-only SELECT is fine; any write needs explicit authorization.
4. **`sync` must carry `--corr_nr`** — otherwise it triggers an interactive transport-request prompt.
5. **Non-interactive `delete` needs piped confirm**`echo "yes" | python main.py delete ...` (else EOFError).
6. **DDIC `sync` failure on NW 7.40 (HTTP 406) is a system limit** — do not retry; report to the user for SE09.
### 1. 单对象编辑同步
## 🔄 Standard WorkflowSAP 对象操作标准流程)
> 所有 SAP 对象修改操作必须按此顺序执行,不得跳过或调整步骤顺序。
```
步骤1 info — 确认程序存在、已激活、关联请求号
├─ 有请求号 → 记录
└─ 无请求号 → transport list 找老板的请求号推荐
→ 无合适的则帮老板新建(含请求描述)
步骤2 download — 源码下载到本地
步骤3 五角色流水线:研究员 → 创作者 → 质检 → 老板批准
步骤4 建 Gitee 仓库(ADT 层级:src/包/类型/对象/)+ 同步确认请求号
└─ 请求号写入 doc 文档
步骤5 工程师改代码(本地,不 commit)
步骤6 请求号绑定对象(如 SE09 未自动绑定)
步骤7 sync --corr_nr → 语法检查 → 迭代修复 → 激活成功
└─ 全程带请求号,不要到编译阶段才关联
步骤8 激活成功 → git commit + tag → push
└─ git = 细粒度变更追溯 / 请求号 = 系统大版本管理
```bash
python main.py download --name ZMY_CLASS --type class --path ./src
# 用编辑器修改 ./src/zmy_class.abap
python main.py sync --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
```
**核心原则:**
- info 同时查请求号,不单独处理
- 建 Gitee 仓库同时确认请求号,不滞后
- sync 全程带 `--corr_nr`,不等编译阶段补
- 只有激活成功才 git commit,不是每次本地改就提交
- git 管变更细节,请求号管系统大版本
### 2. 批量项目同步
## 📦 Gitee仓库规范
### 目录结构(ADT 层级)
每个 SAP 项目的 Gitee 仓库按 ADT 层级组织源码:
```
sap-<project-name>/
├── src/
│ └── <package>/ # SAP 包名(如 $ZIDTR
│ ├── report/ # 程序类型目录
│ │ └── ZXXX.abap # ABAP 源码(.abap 扩展名)
│ ├── class/
│ │ └── ZCL_XXX.abap
│ ├── interface/
│ │ └── ZIF_XXX.abap
│ ├── function/
│ │ └── ZGROUP_ZFUNC.abap
│ ├── domain/
│ ├── dataelement/
│ ├── table/
│ └── ...
├── doc/ # 文档(请求号记录、设计说明)
│ └── transport-<corr_nr>.md # 每次传输的请求号记录
├── references/ # 参照文档
└── log/ # 错误知识库
```bash
python main.py init --path ./my_project
# 添加对象到 src/ 目录
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX
```
### 请求号同步确认
### 3. DDIC 对象创建
1. **创建仓库时**:确认传输请求号(`transport list``transport info --corr_nr DEVK9XXXXX`),写入 `doc/transport-<corr_nr>.md`
2. **每次 sync 前**:确认请求号仍有效(未释放、未满),检查 `transport objects --corr_nr DEVK9XXXXX` 确保对象已绑定
3. **激活成功后**:更新 `doc/` 记录本次变更摘要
4. **仓库命名建议**`sap-<package-name>`,小写、hyphen 分隔,如 `sap-zidtradmin`
```bash
# 准备 JSON 定义文件
python main.py create --name ZMY_DOMAIN --type domain --definition domain.json --corr_nr DEVK901XXX
```
### Git 提交时机
### 4. 数据查询
- **只有 `sync` 激活成功后才 git commit**,不是每次本地修改就提交
- commit message 包含:对象名 + 请求号 + 变更摘要
- tag 使用请求号命名:`DEVK9XXXXX`
- git 管变更细节追溯,请求号管系统大版本管理
```bash
python main.py show-table --name ZMY_TABLE # 结构
python main.py read-table --name ZMY_TABLE --max-rows 100 # 数据
```
## Error Handling (Quick Reference)
### 5. 远程执行 ABAP 程序
错误知识库: `log/` 目录(LLM-WIKI 格式),每个错误独立一页。**先查 log/,再查 `references/error-handling.md`**。新错误按 `log/SCHEMA.md` 规范入库。
```bash
python main.py run-program --name ZSAPILOT_SETUP
```
Full flow: `references/error-handling.md`.
### 6. 分步操作(上传→检查→激活)
| Error Pattern | Action |
|---|---|
| HTTP 406 (DDIC Lock) | System limit — report to user → SE09 manual DDIC sync |
| HTTP 403 (Locked) | Residual enqueue lock — try `run-program` clear-locks report, then SM12 |
| HTTP 400 (SaveFailure) | Class DEFINITION mismatch — diff local vs SAP source |
| HTTP 404 (DDIC download/sync) | NW 7.40 has no `/source/main` endpoint (7.50+) — DDIC objects only create/info/delete |
| HTTP 423 (Transport lock) | Object bound to transport — report to user → SE09 |
| HTTP 500 (set_source) | Lock 成功但写入 500,传输请求中无对象 → SE09 手动添加对象到请求 → `log/adt-set-source-500.md` |
| Activate fails, still inactive | double-activate built-in; still fails → report → SE09 |
| Open SQL @ 转义不一致 | 语法检查 2 错误——一处用 `@` 则全局必须一致(`WHERE field = @lv_var``INTO TABLE @lt_data``log/adt-nw740-open-sql-consistency.md` |
| `WITH EMPTY KEY` dump | NW 7.40 unsupported — use `WITH NON-UNIQUE KEY` / `WITH DEFAULT KEY` |
| Function name error | Missing `/` separator — use `ZGROUP/Z_FUNC` |
| EOFError on delete | Non-interactive — prepend `echo "yes" \|` |
```bash
# 适合锁定冲突频繁的自动化场景:先安全上传,稍后再激活
python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
General flow: **diagnose** (params / file / transport / network / object existence) → **usage error**: fix & retry → **sap-cli limit**: report, never bypass → **SAP limit**: exhaust automation below → **confirmed un-automatable**: request SAP GUI op.
# 修改表结构后验证关联程序语法
python main.py syntax-check --name ZDEPENDENT_REPORT --type report
Automation fallbacks — exhaust in order before requesting any SAP GUI action:
1. **Adjust params** — different `--corr_nr`, or `--dry-run` to preview a batch before committing.
2. **`run-program` workaround** — sync + run a helper report (e.g. `ZSAPILOT_CLEAR_LOCKS`) to clear residual enqueue locks.
3. **`delete` + recreate** — only if the object needs a full rewrite; `delete` may fail with HTTP 423 (transport-bound) → then it is SE09.
4. **`activate` retry** — double-activate is built in; if it still fails, stop retrying and report — do not chase other activation strategies.
# 确认无问题后单独激活
python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX
```
- **Never bypass sap-cli** (no requests / curl / ADTClient) — report the failure and its cause.
- **Give the complete change checklist at once** — do the full comparison first (`show-table` vs code field refs); never surface issues incrementally.
- **System-table writes** — 4-step authorization: explain *** → list exact statements → state risks & alternatives → await explicit approval.
---
## NW 7.40 Compatibility (E2E verified — v2.3.0)
## ⚠️ 使用约束(必须遵守)
Full matrix: `docs/NW740-COMPATIBILITY.md`.
> 详细规则见 `references/sap-tool-constraints.md` 和 `references/error-handling.md`
| Availability | Types |
|---|---|
| Full (download → edit → sync closed loop) | `report`, `class`, `interface`, `function`, `functiongroup`, `include` |
| CRUD only (create/info/delete, no source edit) | `domain`, `dataelement` — DDIC stored as XML at object URI, no `/source/main` endpoint (7.50+ feature) |
| Endpoint missing (404/415) | `table`, `structure`, `tabletype`, `view`, `messageclass`, `searchhelp`, `lockobject`, `cdsview` |
1. **只使用 `python main.py <命令>` 操作 SAP** — 禁止用 requests、curl 直接调 ADT/SOAP
2. **禁止修改 SAP 标准对象**`CL_*``SAPL*` 等)— 只允许只读操作
3. **禁止未授权操作系统表**TADIR、E071、SEOCLASS 等)— SELECT 诊断可以
4. **sync 必须带 `--corr_nr`** — 否则触发交互提示
5. **非交互 delete 用管道确认**`echo "yes" | python main.py delete ...`(否则 EOFError
NW 7.40 specifics:
- **DDIC Lock** — `delete` fixed (dedicated Accept header); `sync` still 406.
- **Activation false-negative** — first activate may report failure but actually succeed; double-activate is built in.
- **ABAP syntax limits** — no string templates `\|...\|`, no inline `DATA(...)`, no `WITH EMPTY KEY`.
- **Open SQL @ 转义一致性** — NW 7.40 要求一处用 `@` 则全 SQL 必须一致(`WHERE field = @lv_var``INTO TABLE @lt_data`),混用导致语法检查 2 错误。详见 `log/adt-nw740-open-sql-consistency.md`
- **set_source HTTP 500(传输请求无对象)** — Lock 成功但写入 500,根因:Lock API 创建锁句柄但未绑定对象到传输请求。NW 7.40 需 SE09 手动添加对象。详见 `log/adt-set-source-500.md`
- **CSRF token** — all writes require it; sap-cli handles automatically.
---
## 常见问题速查
| 问题 | 原因 | 解决 |
|------|------|------|
| HTTP 406DDIC sync Lock | NW 7.40 DDIC sync 路径 lock 仍 406delete 已修复) | 报告用户 → SE09 手动同步 DDIC |
| HTTP 403Locked | 残留 enqueue lock | 先试 `run-program` 清锁,不行 → SM12 |
| HTTP 400SaveFailure | 类 DEFINITION 与 SAP 不一致 | 对比本地 vs SAP 源码 |
| HTTP 404DDIC download/sync | NW 7.40 无 `/source/main` 端点(7.50+ 特性) | DDIC 对象只能 create/info/delete,不能 download/sync |
| HTTP 423Transport lock | 对象绑定在传输请求中 | 报告用户 → SE09 |
| 激活失败 | NW 7.40 ADT 限制 | sap-cli 内置 double-activate,仍失败 → SE09 |
| `WITH EMPTY KEY` dump | NW 7.40 不支持 | 改用 `WITH NON-UNIQUE KEY` |
| 函数名格式错误 | 缺少 `/` 分隔符 | 使用 `ZGROUP/Z_FUNC` 格式 |
## NW 7.40 兼容性(真实系统 E2E 验证 — v2.2.1
> 完整矩阵见 `docs/NW740-COMPATIBILITY.md`
### 完整可用(download → 编辑 → sync 全闭环)
`report` · `class` · `interface` · `function` · `functiongroup` · `include`
### 仅 CRUD 可用(create/info/delete,无源码编辑)
`domain` · `dataelement` — NW 7.40 的 DDIC 对象以 XML 存储于对象 URI,没有独立的 source/main 端点(7.50+ 特性)。即便 Eclipse ADT 在 7.40 上也无法编辑 DDIC 源码。
### 端点缺失(代码已就位,待更高版本验证)
`table` · `structure` · `tabletype` · `view` · `messageclass` · `searchhelp` · `lockobject` · `cdsview` — NW 7.40 ADT 端点层返回 404/415,请求未到达 XML 解析。
### 其他 NW 7.40 特性
- **DDIC Lock**delete 已修复(专用 Accept 头),sync 路径仍 406
- **激活误报**:首次激活可能报失败但实际成功,sap-cli 已内置 double-activate
- **ABAP 语法限制**:不支持字符串模板 `\|...\|`、inline 声明 `DATA(...)``WITH EMPTY KEY`
- **CSRF Token**:所有写操作必须携带,sap-cli 自动处理
---
## 分发与安装(团队成员)
### 方式 1Hermes Agent
```bash
git clone <gitee-url> ~/AppData/Local/hermes/skills/productivity/sap-cli
python ~/AppData/Local/hermes/skills/productivity/sap-cli/scripts/setup.py all
```
### 方式 2Claude Code
```bash
git clone <gitee-url> ~/.claude/skills/sap-cli
python ~/.claude/skills/sap-cli/scripts/setup.py all
```
安装后验证:
```bash
cd <SKILL_DIR>/assets && python main.py --help
```
+1 -1
View File
@@ -1 +1 @@
2.2.1
2.3.0
+1 -1
View File
@@ -2,4 +2,4 @@
sap-cli — 基于 SAP ADT REST API 的 ABAP 开发对象管理工具
"""
__version__ = "2.2.1"
__version__ = "2.3.0"
+2
View File
@@ -158,6 +158,8 @@ CDS View:
tr_release.add_argument("--corr_nr", required=True, help="传输请求编号")
tr_objects = transport_sub.add_parser("objects", help="列出传输请求中的对象")
tr_objects.add_argument("--corr_nr", required=True, help="传输请求编号")
tr_create = transport_sub.add_parser("create", help="新建传输请求")
tr_create.add_argument("--description", required=True, help="传输请求描述")
# ── check (ATC 代码检查) ──
check_parser = subparsers.add_parser("check", help="ATC 代码检查")
+39 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import argparse
import logging
import os
import re
import xml.etree.ElementTree as ET
from sapcli.client import ADTClient
@@ -18,7 +19,7 @@ from sapcli.exceptions import (
SapCliError,
)
from sapcli.manifest import Manifest, ManifestEntry
from sapcli.types import get_type_config, parse_object_name
from sapcli.types import get_r3tr_object_code, get_type_config, parse_object_name
DDIC_TYPES = {"domain", "dataelement", "table", "structure", "tabletype"}
@@ -401,6 +402,30 @@ def _update_manifest_after_sync(
logger.warning("更新清单失败: %s", e)
def _query_transport_request(
client: ADTClient, obj_type: str, display_name: str
) -> str:
"""查对象当前所在(未释放)的传输请求号。纯只读查 E071;失败优雅降级。"""
r3tr_code = get_r3tr_object_code(obj_type)
if not r3tr_code:
return "—(非独立传输对象)"
obj_name_upper = display_name.upper().split("/")[-1]
if not re.fullmatch(r"[A-Za-z0-9_/$]+", obj_name_upper):
return "(对象名非法,跳过)"
try:
sql = (
f"SELECT trkorr FROM e071 WHERE pgmid='R3TR' "
f"AND object='{r3tr_code}' AND obj_name='{obj_name_upper}' "
f"UP TO 10 ROWS"
)
result = client.query_table_data(sql, max_rows=10)
trkorrs = sorted({row[0] for row in result.get("rows", []) if row and row[0]})
return ", ".join(trkorrs) if trkorrs else ""
except Exception:
logger.debug("查询 E071 传输请求失败", exc_info=True)
return "(无法获取)"
def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
"""查询对象元数据信息。"""
obj_type: str = args.type
@@ -469,6 +494,14 @@ def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
info_responsible = root.attrib.get(f"{{{ns['adtcore']}}}responsible", "")
info_language = root.attrib.get(f"{{{ns['adtcore']}}}masterLanguage", "")
# 开发包:解析对象 XML 自带的 packageRef 子元素
pkg_ref = root.find(f".//{{{ns['adtcore']}}}packageRef")
info_package = (
pkg_ref.attrib.get(f"{{{ns['adtcore']}}}name", "")
if pkg_ref is not None
else ""
)
if info_version == "active":
status_icon = "✓ 已激活"
elif info_version == "inactive":
@@ -476,6 +509,9 @@ def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
else:
status_icon = f"{info_version}"
# 传输请求号:纯只读查 E071(不在对象元数据 XML 中)
corr_display = _query_transport_request(client, obj_type, parsed.display_name)
print(f" ✓ 查询成功\n")
print(f" {'' * 50}")
print(f" 名称: {info_name}")
@@ -484,6 +520,8 @@ def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
print(f" 状态: {status_icon}")
print(f" 负责人: {info_responsible}")
print(f" 语言: {info_language}")
print(f" 开发包: {info_package or '(未指定)'}")
print(f" 传输请求: {corr_display}")
print(f" 创建者: {info_created_by}")
print(f" 修改者: {info_changed_by}")
print(f" 修改时间: {info_changed_at}")
+26 -1
View File
@@ -21,12 +21,15 @@ def cmd_transport(args: argparse.Namespace, client: ADTClient) -> None:
_transport_release(args, client)
elif action == "objects":
_transport_objects(args, client)
elif action == "create":
_transport_create(args, client)
else:
print(" 用法: sap-cli transport [list|info|release|objects]")
print(" 用法: sap-cli transport [list|info|release|objects|create]")
print(" list — 列出可修改的传输请求")
print(" info — 查看传输请求详情")
print(" release — 释放传输请求")
print(" objects — 列出传输请求中的对象")
print(" create — 新建传输请求 (--description)")
def _transport_list(args: argparse.Namespace, client: ADTClient) -> None:
@@ -107,6 +110,28 @@ def _transport_release(args: argparse.Namespace, client: ADTClient) -> None:
print(f" ✗ 释放失败,请检查传输请求状态")
def _transport_create(args: argparse.Namespace, client: ADTClient) -> None:
"""新建传输请求。"""
description: str = args.description
print("=" * 60)
print(" SAP 新建传输请求")
print("=" * 60)
print(f" 描述: {description}")
print(f"\n → 正在创建...")
try:
new_nr = client.create_transport_request(description)
except Exception as e:
print(f" ✗ 创建失败: {e}")
return
if new_nr:
print(f"\n ✓ 传输请求已创建: {new_nr}")
else:
print(f"\n ✗ 创建失败(未返回编号),请检查系统或描述合法性")
def _transport_objects(args: argparse.Namespace, client: ADTClient) -> None:
"""列出传输请求中的对象。"""
corr_nr: str = args.corr_nr
+29
View File
@@ -227,6 +227,35 @@ def all_type_keys() -> list[str]:
return list(_OBJECT_TYPE_REGISTRY.keys())
# sap-cli 类型 → SAP 传输系统 R3TR 对象代码(用于查 E071 表)。
# None 表示该类型非独立 R3TR 对象,查 E071 无意义。
_R3TR_OBJECT_CODE: dict[str, str | None] = {
"report": "PROG",
"class": "CLAS",
"interface": "INTF",
"function": None, # 函数模块属函数组(FUGR),非独立 R3TR
"functiongroup": "FUGR",
"domain": "DOMA",
"dataelement": "DTEL",
"table": "TABL",
"structure": "TABL",
"tabletype": "TTYP",
"include": None, # Include 属所属程序,非独立 R3TR
"cdsview": "DDLS",
"messageclass": "MSAG",
"view": "VIEW",
"searchhelp": "SHLP",
"lockobject": "ENQU",
"dcl": "DCLS",
"ddlX": "DDLX",
}
def get_r3tr_object_code(key: str) -> str | None:
"""返回对象类型对应的 SAP R3TR 传输对象代码;无独立代码返回 None。"""
return _R3TR_OBJECT_CODE.get(key)
@dataclass(frozen=True)
class ParsedName:
obj_uri: str