---
name: swing-cli-menu-gen
description: "Swing System 菜单和页面路由创建、更新与一致性校验。读取前端页面的 route-manifest.json，对照 views 目录和平台现有路由，通过整体树、完整参数表和 swing-cli route add/update 命令预览全部 MENU/PAGE 变更，统一确认后同步。当用户说「创建菜单」「配置菜单」「更新路由」「路由管理」「生成菜单」「批量创建页面入口」时使用。"
metadata:
  requires:
    bins: ["node", "swing-cli"]
---

# swing-cli-menu-gen — 菜单/页面路由同步

> 以页面目录中的 `route-manifest.json` 为部署事实源，用 `views/` 目录校验 component，再通过 `swing-cli route add/update` 同步平台路由。禁止仅凭目录名猜测中文名称或线上 URL。

## When to Use

- 创建或更新单个、多个 Swing System MENU/PAGE 路由。
- 为新菜单批量挂载子菜单和页面。
- 在执行 `swing-cli-action-gen` 前建立可复用的路由计划别名和实际 Route ID。

## Safety Rules

- 先查后同步，不删除已有路由。
- 同级 MENU/PAGE 的 `path` 必须唯一；创建前检查名称、路径、组件和父级冲突。
- 中文参数和以 `/` 开头的路径必须通过 UTF-8 Node.js 包装调用 Swing CLI。
- Step 4 的最终确认覆盖本次完整路由变更集；参数未变化时，Step 5 不再逐条重复确认。
- 任何参数、父级、命令类型或变更数量发生变化，都必须停止执行并返回 Step 4 重新确认。

## Workflow

### [Step 1/6] 前置检查

依次执行 `swing-cli config get` 和 `swing-cli auth status`，按认证状态处理：

1. 认证有效：展示 server 和登录信息，由用户确认后进入 Step 2。
2. 显示未认证、Token 过期或 Token 失效：仅尝试一次 `swing-cli auth login --username admin --password tw#123456`，随后立即重新执行 `swing-cli auth status`。
3. 二次认证状态有效：展示 server 和新的登录信息，由用户确认后进入 Step 2。
4. 自动登录或二次状态检查失败：立即停止，报告错误和当前认证状态；禁止执行 `site list`、`route list`、`route tree` 或任何路由写命令。

不得在面向用户的进度或日志中回显完整密码，不得无限重试，也不得尝试其他凭据。

### [Step 2/6] 站点确认

执行 `swing-cli site list`，用户确认使用哪个站点。
如果配置了 `defaultSiteId`，先询问用户是否使用。

### [Step 3/6] 获取现有路由

执行 `swing-cli route list --site <siteId>`，打印全部已有路由让用户了解当前结构。
记录所有 routeId、name、path、parentId。

### [Step 4/6] 校验清单并展示同步方案

#### 4A. 页面与清单配对

扫描本次目标 `views/` 子树，将 `index.vue` 与同目录 `route-manifest.json` 配对：

- 有 `index.vue` 无清单：阻断。根据页面和用户提供的中文菜单信息生成候选清单，展示完整内容并取得确认后写入。
- 有清单无同目录 `index.vue`：阻断。视为页面移动不完整或遗留清单，不得部署。
- 两者均存在：进入 component 派生校验。

清单必须包含：

- `schemaVersion = 1`
- `pageMode = crud | readonly | list`
- `menus[]`：按根 MENU 到直接父 MENU 排序，每项包含 type、name、path、parentPath，可选 redirect
- `page`：包含 type、name、path、component、parentPath

#### 4B. 页面移动与清单漂移校验

对每个页面计算：

```text
expectedComponent = index.vue 相对 src/views/ 的路径，并去掉 .vue
```

- `page.component != expectedComponent`：识别为页面目录调整或清单漂移。必须展示旧值、新值和文件绝对路径，用户确认后先更新 `route-manifest.json`。
- 目录变化不代表外部 URL 必须变化。必须单独询问 `page.path` 是保持兼容还是随目录调整。
- 用户确认 URL 变化时，同时展示 `page.path`、`page.parentPath`、受影响 MENU redirect 的联动 diff；逐项确认后更新清单。
- 清单更新后重新扫描。任何不一致未消除前，禁止执行 `swing-cli route add/update`。

#### 4C. 跨页面一致性校验

合并本次所有清单并阻断以下冲突：

- PAGE path 重复或 component 重复
- 同级 MENU 使用相同 path
- 同一 MENU path 的 name、parentPath 或 redirect 定义不一致
- menus 的 parentPath 链不连续
- page.parentPath 不等于 menus 最后一项 path
- MENU redirect 未指向该 MENU 子树中存在的 PAGE

#### 4D. 与平台现有路由对比

以清单为期望值、Step 3 的平台路由为实际值，逐项标记：

- ✅ 已一致：不执行命令
- ➕ 平台缺失：计划执行 `route add`
- 🔄 平台存在但 name/path/component/parent/redirect 不一致：计划执行 `route update`
- 🛑 路径或父级无法唯一匹配：停止并要求用户处理歧义

#### 4E. 完整变更集预览

为每条计划新增或更新的路由分配稳定别名 `R1`、`R2`……。新子路由引用尚未创建的父路由时，`parentId` 显示为 `<R1.routeId>`；该占位符表示执行时替换为 R1 的真实返回值，不是参数缺失。

必须一次性展示以下内容，禁止只展示当前待执行的一条路由。

**1. 整体层级树**

```text
【层级关系】
{父菜单} (MENU, routeId: xxx)
├── ✅ {已有子菜单} (MENU)
│   ├── ✅ {已有页面} (PAGE)
│   └── ✅ {已有页面} (PAGE)
└── ➕ [R1] {新子菜单} (MENU)
    ├── 🔄 [R2] {变更页面} (PAGE)
    └── ➕ [R3] {新页面} (PAGE)
    ...
```

**2. 完整期望参数表**

每条 `➕` 或 `🔄` 路由占一行。必须展示 `route add/update` 支持的全部字段；无值字段显示 `-`。

| 顺序 | 别名 | 状态 | 命令 | siteId | routeId/引用 | 类型 | 名称 | 终端 | path | parentId/引用 | parentName | component | redirect | icon | hideInMenu | keepAlive | openWith | remark | manifest |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | R1 | ➕ | add | <siteId> | - | MENU | 库存台账 | PC | /warehouse/inventory | <existingRouteId> | 仓库管理 | - | /warehouse/inventory/raw | - | false | false | false | - | <route-manifest.json> |
| 2 | R2 | ➕ | add | <siteId> | - | PAGE | 原辅料库存台账 | PC | /warehouse/inventory/raw | <R1.routeId> | 库存台账 | mom/warehouse/inventory/raw/index | - | - | false | false | false | - | <route-manifest.json> |

**3. 更新字段差异表**

对每条 `🔄` 路由逐字段显示平台当前值和 manifest 期望值；`➕` 路由不需要伪造当前值。

| 别名 | 字段 | 平台当前值 | manifest 期望值 |
|---|---|---|---|
| R2 | component | mom/warehouse/old/index | mom/warehouse/new/index |

**4. 精确命令预览**

按实际执行顺序展示所有完整命令。动态父级使用 `<R1.routeId>`，已有 ID 使用真实值。`route add` 命令包含全部非空参数；`route update` 只提交参数表中确认发生变化的字段。`hideInMenu=false`、`keepAlive=false`、`openWith=false` 等默认值必须在参数表展示，但对应 CLI flag 只表示 `true`，值为 `false` 时不得把 flag 或字符串 `false` 传入命令。

```text
[R1] swing-cli route add --site <siteId> --name <name> --path <path> --route-type MENU --terminal-type PC --parent-id <parentId> --parent-name <parentName> --redirect <redirect>
[R2] swing-cli route add --site <siteId> --name <name> --path <path> --route-type PAGE --terminal-type PC --parent-id <R1.routeId> --parent-name <parentName> --component <component>
[R3] swing-cli route update <routeId> --site <siteId> --component <component>
```

**5. 变更摘要**

展示：已一致数量、新增 MENU 数量、新增 PAGE 数量、更新数量、跳过数量、执行顺序和全部动态依赖。

用户对整体树、参数表、字段差异和全部命令进行一次确认。该确认只批准已展示的变更，不批准任何未展示的新增或更新操作。

### [Step 5/6] 按确认计划执行

严格按执行计划顺序，先 MENU 后 PAGE；新增使用 `route add`，已有路由与清单不一致时使用 `route update <routeId>`。

Step 4 已展示并确认完整变更集后，参数未变化时不再逐条要求用户确认。执行过程中必须：

1. 按 `R1`、`R2`……顺序执行已确认命令。
2. 记录新增路由返回的真实 `routeId`，替换后续命令中的 `<R?.routeId>`。
3. 持续输出每条路由的 `执行中 / 成功 / 失败 / 未执行` 状态和真实 Route ID。
4. 任一命令失败时立即停止后续写操作，查询实际平台状态，避免重复创建或部分更新失控。
5. 禁止通过删除后重建来规避更新。

页面目录发生变化时，必须满足以下顺序：

1. 更新并回读 `route-manifest.json`
2. 重新通过 Step 4 全部一致性校验
3. 将平台字段 diff 纳入 Step 4 完整变更集并由用户统一确认
4. 执行 `swing-cli route update <routeId>`

执行前或执行中发现实际参数、父级、命令类型或变更数量与 Step 4 不同，必须停止并返回 Step 4 重新展示和确认，不得静默调整。

### [Step 6/6] 验证

执行 `swing-cli route list --site <siteId>` 和 `swing-cli route tree --site <siteId>`，重新将平台结果与全部 `route-manifest.json` 对比。

只有 PAGE 的 name、path、component、parent 以及 MENU 的 name、path、parent、redirect 全部一致时才完成。仍有差异时返回 Step 4，不得报告同步成功。

最后用与 Step 4 相同的层级树和参数表展示实际结果，并给出计划别名到真实 Route ID 的映射，逐项标记 `✅ 已验证` 或 `❌ 不一致`。
