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 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" version: "2.3.0"
author: WuRangyu author: WuRangyu
license: MIT 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 ```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 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 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 delete --name ZMY_CLASS --type class
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX
# 3. Table query: structure (SE11) + data (SE16N) # 单独激活(sap-cli 内置 NW 7.40 double-activate
python main.py show-table --name ZMY_TABLE python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX
python main.py read-table --name ZMY_TABLE --fields "F1,F2" --where "F1 = 'X'" --max-rows 50
# 单独上传(仅锁定→写入→解锁,不检查不激活)
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 ```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 init --path ./my_project
python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX # activate once clean
# 刷新对象状态
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` | ✅ | | OOP | `class`, `interface` | ✅ |
| Functions | `function` | ✅ | | 函数 | `function` | ✅ |
| DDIC base | `domain`, `dataelement`, `table`, `structure` | ✅ | | DDIC 基础 | `domain`, `dataelement`, `table`, `structure` | ✅ |
| DDIC ext | `cdsview`, `view` | ✅ | | DDIC 扩展 | `cdsview`, `view` | ✅ |
| DDIC no-source | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ | | DDIC 无源码 | `tabletype`, `messageclass`, `searchhelp`, `lockobject` | ❌ |
| Groups | `functiongroup` | ❌ | | 程序组 | `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. ### 1. 单对象编辑同步
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.
## 🔄 Standard WorkflowSAP 对象操作标准流程) ```bash
python main.py download --name ZMY_CLASS --type class --path ./src
> 所有 SAP 对象修改操作必须按此顺序执行,不得跳过或调整步骤顺序。 # 用编辑器修改 ./src/zmy_class.abap
python main.py sync --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
```
步骤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 = 细粒度变更追溯 / 请求号 = 系统大版本管理
``` ```
**核心原则:** ### 2. 批量项目同步
- info 同时查请求号,不单独处理
- 建 Gitee 仓库同时确认请求号,不滞后
- sync 全程带 `--corr_nr`,不等编译阶段补
- 只有激活成功才 git commit,不是每次本地改就提交
- git 管变更细节,请求号管系统大版本
## 📦 Gitee仓库规范 ```bash
python main.py init --path ./my_project
### 目录结构(ADT 层级) # 添加对象到 src/ 目录
python main.py sync --all --path ./my_project --corr_nr DEVK901XXX
每个 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/ # 错误知识库
``` ```
### 请求号同步确认 ### 3. DDIC 对象创建
1. **创建仓库时**:确认传输请求号(`transport list``transport info --corr_nr DEVK9XXXXX`),写入 `doc/transport-<corr_nr>.md` ```bash
2. **每次 sync 前**:确认请求号仍有效(未释放、未满),检查 `transport objects --corr_nr DEVK9XXXXX` 确保对象已绑定 # 准备 JSON 定义文件
3. **激活成功后**:更新 `doc/` 记录本次变更摘要 python main.py create --name ZMY_DOMAIN --type domain --definition domain.json --corr_nr DEVK901XXX
4. **仓库命名建议**`sap-<package-name>`,小写、hyphen 分隔,如 `sap-zidtradmin` ```
### Git 提交时机 ### 4. 数据查询
- **只有 `sync` 激活成功后才 git commit**,不是每次本地修改就提交 ```bash
- commit message 包含:对象名 + 请求号 + 变更摘要 python main.py show-table --name ZMY_TABLE # 结构
- tag 使用请求号命名:`DEVK9XXXXX` python main.py read-table --name ZMY_TABLE --max-rows 100 # 数据
- git 管变更细节追溯,请求号管系统大版本管理 ```
## 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 | ```bash
|---|---| # 适合锁定冲突频繁的自动化场景:先安全上传,稍后再激活
| HTTP 406 (DDIC Lock) | System limit — report to user → SE09 manual DDIC sync | python main.py upload --name ZMY_CLASS --type class --path ./src/zmy_class.abap --corr_nr DEVK901XXX
| 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" \|` |
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. python main.py activate --name ZMY_CLASS --type class --corr_nr DEVK901XXX
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.
- **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 | 1. **只使用 `python main.py <命令>` 操作 SAP** — 禁止用 requests、curl 直接调 ADT/SOAP
|---|---| 2. **禁止修改 SAP 标准对象**`CL_*``SAPL*` 等)— 只允许只读操作
| Full (download → edit → sync closed loop) | `report`, `class`, `interface`, `function`, `functiongroup`, `include` | 3. **禁止未授权操作系统表**TADIR、E071、SEOCLASS 等)— SELECT 诊断可以
| CRUD only (create/info/delete, no source edit) | `domain`, `dataelement` — DDIC stored as XML at object URI, no `/source/main` endpoint (7.50+ feature) | 4. **sync 必须带 `--corr_nr`** — 否则触发交互提示
| Endpoint missing (404/415) | `table`, `structure`, `tabletype`, `view`, `messageclass`, `searchhelp`, `lockobject`, `cdsview` | 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 开发对象管理工具 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_release.add_argument("--corr_nr", required=True, help="传输请求编号")
tr_objects = transport_sub.add_parser("objects", help="列出传输请求中的对象") tr_objects = transport_sub.add_parser("objects", help="列出传输请求中的对象")
tr_objects.add_argument("--corr_nr", required=True, 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 (ATC 代码检查) ──
check_parser = subparsers.add_parser("check", help="ATC 代码检查") check_parser = subparsers.add_parser("check", help="ATC 代码检查")
+39 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import argparse import argparse
import logging import logging
import os import os
import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from sapcli.client import ADTClient from sapcli.client import ADTClient
@@ -18,7 +19,7 @@ from sapcli.exceptions import (
SapCliError, SapCliError,
) )
from sapcli.manifest import Manifest, ManifestEntry 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"} DDIC_TYPES = {"domain", "dataelement", "table", "structure", "tabletype"}
@@ -401,6 +402,30 @@ def _update_manifest_after_sync(
logger.warning("更新清单失败: %s", e) 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: def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
"""查询对象元数据信息。""" """查询对象元数据信息。"""
obj_type: str = args.type 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_responsible = root.attrib.get(f"{{{ns['adtcore']}}}responsible", "")
info_language = root.attrib.get(f"{{{ns['adtcore']}}}masterLanguage", "") 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": if info_version == "active":
status_icon = "✓ 已激活" status_icon = "✓ 已激活"
elif info_version == "inactive": elif info_version == "inactive":
@@ -476,6 +509,9 @@ def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
else: else:
status_icon = f"{info_version}" status_icon = f"{info_version}"
# 传输请求号:纯只读查 E071(不在对象元数据 XML 中)
corr_display = _query_transport_request(client, obj_type, parsed.display_name)
print(f" ✓ 查询成功\n") print(f" ✓ 查询成功\n")
print(f" {'' * 50}") print(f" {'' * 50}")
print(f" 名称: {info_name}") print(f" 名称: {info_name}")
@@ -484,6 +520,8 @@ def cmd_info(args: argparse.Namespace, client: ADTClient) -> None:
print(f" 状态: {status_icon}") print(f" 状态: {status_icon}")
print(f" 负责人: {info_responsible}") print(f" 负责人: {info_responsible}")
print(f" 语言: {info_language}") print(f" 语言: {info_language}")
print(f" 开发包: {info_package or '(未指定)'}")
print(f" 传输请求: {corr_display}")
print(f" 创建者: {info_created_by}") print(f" 创建者: {info_created_by}")
print(f" 修改者: {info_changed_by}") print(f" 修改者: {info_changed_by}")
print(f" 修改时间: {info_changed_at}") 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) _transport_release(args, client)
elif action == "objects": elif action == "objects":
_transport_objects(args, client) _transport_objects(args, client)
elif action == "create":
_transport_create(args, client)
else: else:
print(" 用法: sap-cli transport [list|info|release|objects]") print(" 用法: sap-cli transport [list|info|release|objects|create]")
print(" list — 列出可修改的传输请求") print(" list — 列出可修改的传输请求")
print(" info — 查看传输请求详情") print(" info — 查看传输请求详情")
print(" release — 释放传输请求") print(" release — 释放传输请求")
print(" objects — 列出传输请求中的对象") print(" objects — 列出传输请求中的对象")
print(" create — 新建传输请求 (--description)")
def _transport_list(args: argparse.Namespace, client: ADTClient) -> None: def _transport_list(args: argparse.Namespace, client: ADTClient) -> None:
@@ -107,6 +110,28 @@ def _transport_release(args: argparse.Namespace, client: ADTClient) -> None:
print(f" ✗ 释放失败,请检查传输请求状态") 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: def _transport_objects(args: argparse.Namespace, client: ADTClient) -> None:
"""列出传输请求中的对象。""" """列出传输请求中的对象。"""
corr_nr: str = args.corr_nr 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()) 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) @dataclass(frozen=True)
class ParsedName: class ParsedName:
obj_uri: str obj_uri: str