zero-ai 1.0.81 → 1.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.r2mo/task/2026-03-02/2026-03-02.09-48-15-TASK@/345/274/200/345/217/221/346/226/260/345/221/275/344/273/244ai ex-app.md" +65 -0
- package/.r2mo/task/task-001.md +2 -142
- package/.r2mo/task/task-004.md +4 -0
- package/.r2mo/task/task-005.md +4 -0
- package/package.json +1 -1
- package/src/commander/ex-app.json +6 -0
- package/src/commander-ai/fn.ex.app.js +234 -0
- package/src/commander-ai/index.js +2 -0
- package/r2mo-init/.obsidian/app.json +0 -1
- package/r2mo-init/.obsidian/appearance.json +0 -10
- package/r2mo-init/.obsidian/community-plugins.json +0 -7
- package/r2mo-init/.obsidian/core-plugins.json +0 -33
- package/r2mo-init/.obsidian/plugins/dataview/main.js +0 -20876
- package/r2mo-init/.obsidian/plugins/dataview/manifest.json +0 -11
- package/r2mo-init/.obsidian/plugins/dataview/styles.css +0 -141
- package/r2mo-init/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +0 -815
- package/r2mo-init/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +0 -10
- package/r2mo-init/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +0 -12
- package/r2mo-init/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +0 -1
- package/r2mo-init/.obsidian/plugins/obsidian-kanban/main.js +0 -153
- package/r2mo-init/.obsidian/plugins/obsidian-kanban/manifest.json +0 -11
- package/r2mo-init/.obsidian/plugins/obsidian-kanban/styles.css +0 -1
- package/r2mo-init/.obsidian/plugins/obsidian-plantuml/main.js +0 -7732
- package/r2mo-init/.obsidian/plugins/obsidian-plantuml/manifest.json +0 -10
- package/r2mo-init/.obsidian/plugins/obsidian-plantuml/styles.css +0 -38
- package/r2mo-init/.obsidian/plugins/obsidian-tasks-plugin/main.js +0 -504
- package/r2mo-init/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +0 -12
- package/r2mo-init/.obsidian/plugins/obsidian-tasks-plugin/styles.css +0 -1
- package/r2mo-init/.obsidian/snippets/body-font.css +0 -9
- package/r2mo-init/.obsidian/themes/Comfort/manifest.json +0 -11
- package/r2mo-init/.obsidian/themes/Comfort/theme.css +0 -218
- package/r2mo-init/.obsidian/themes/Primary/manifest.json +0 -9
- package/r2mo-init/.obsidian/themes/Primary/theme.css +0 -3878
- package/r2mo-init/.obsidian/themes/Retro Windows/manifest.json +0 -7
- package/r2mo-init/.obsidian/themes/Retro Windows/theme.css +0 -582
- package/r2mo-init/.obsidian/themes/RetroOS 98/manifest.json +0 -9
- package/r2mo-init/.obsidian/themes/RetroOS 98/theme.css +0 -2566
- package/r2mo-init/.obsidian/themes/Serenity/manifest.json +0 -7
- package/r2mo-init/.obsidian/themes/Serenity/theme.css +0 -7258
- package/r2mo-init/.obsidian/themes/W95/manifest.json +0 -8
- package/r2mo-init/.obsidian/themes/W95/theme.css +0 -768
- package/r2mo-init/.obsidian/types.json +0 -28
- package/r2mo-init/task/command/ex-api.yaml.example +0 -13
- package/r2mo-init/task/task-002.md +0 -4
- package/r2mo-init/task/task-003.md +0 -4
- package/r2mo-init/task/thread +0 -1
- /package/{r2mo-init/task/task-001.md → .r2mo/task/2026-03-02/2026-03-02.09-23-17-TASK@/345/274/200/345/217/221 ai ex-api /345/221/275/344/273/244.md"} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
runAt: 2026-03-02.09-23-17
|
|
3
|
+
title: 开发新命令ai ex-app
|
|
4
|
+
status: completed
|
|
5
|
+
completedAt: 2026-03-02.09-50-00
|
|
6
|
+
---
|
|
7
|
+
## 执行步骤
|
|
8
|
+
|
|
9
|
+
1. 加载当前目录下 `.r2mo/app.env` 环境变量(可使用 `source` )
|
|
10
|
+
2. 检查数据库核心环境变量(若环境变量缺失中断执行):
|
|
11
|
+
- `Z_DBS_INSTANCE`:数据库实例名
|
|
12
|
+
- `Z_DB_APP_USER`:数据库账号
|
|
13
|
+
- `Z_DB_APP_PASS`:数据库密码
|
|
14
|
+
3. 访问 `X_APP` 数据表,提取 `id`
|
|
15
|
+
4. 检查运行缓存目录
|
|
16
|
+
- 若已设置 `R2MO_HOME` 环境变量,缓存目录为 `R2MO_HOME/apps`
|
|
17
|
+
- 若未设置 `R2MO_HOME` 环境变量,缓存目录为运行命令的目录 `/apps` 下
|
|
18
|
+
5. 汇总缓存中不存在的 `app` 实例(不存在和数据库匹配的 `id` 目录),询问用户是否移除
|
|
19
|
+
6. 用户确认后删除数据库中不存在的缓存目录
|
|
20
|
+
7. 记得打印执行过程中的关键信息,比如移除了多少,加载的环境变量等等
|
|
21
|
+
|
|
22
|
+
## Changes
|
|
23
|
+
|
|
24
|
+
### 新增文件
|
|
25
|
+
1. **src/commander/ex-app.json** (159 bytes)
|
|
26
|
+
- 命令配置文件
|
|
27
|
+
- 定义 executor: `executeExApp`
|
|
28
|
+
- 命令描述:清理缓存目录中不存在于数据库的应用实例
|
|
29
|
+
|
|
30
|
+
2. **src/commander-ai/fn.ex.app.js** (8,471 bytes)
|
|
31
|
+
- 命令实现文件,包含以下功能模块:
|
|
32
|
+
- `loadAppEnv()` - 解析 `.r2mo/app.env` 并加载环境变量
|
|
33
|
+
- `resolveAppEnvPath()` - 智能查找 app.env 路径(支持 ONE/DPA 项目结构)
|
|
34
|
+
- `checkEnv()` - 验证必需的数据库环境变量
|
|
35
|
+
- `getCacheDir()` - 获取缓存目录(R2MO_HOME/apps 或 ./apps)
|
|
36
|
+
- `removeDir()` - 递归删除目录
|
|
37
|
+
- 主流程:加载环境变量 → 验证 → 连接数据库 → 查询 X_APP → 识别孤立目录 → 用户确认 → 删除
|
|
38
|
+
|
|
39
|
+
### 修改文件
|
|
40
|
+
3. **src/commander-ai/index.js**
|
|
41
|
+
- 新增 `const executeExApp = require('./fn.ex.app');`
|
|
42
|
+
- 在 exported 对象中注册 `executeExApp` 函数
|
|
43
|
+
- 添加注释:`// ai ex-app`
|
|
44
|
+
|
|
45
|
+
## 使用方法
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# 查看帮助
|
|
49
|
+
ai ex-app --help
|
|
50
|
+
|
|
51
|
+
# 执行清理(需要在项目根目录或包含 .r2mo/app.env 的目录下运行)
|
|
52
|
+
ai ex-app
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 功能说明
|
|
56
|
+
|
|
57
|
+
命令执行流程:
|
|
58
|
+
1. 自动加载 `.r2mo/app.env` 环境变量(支持 ONE/DPA 项目结构)
|
|
59
|
+
2. 验证必需的数据库环境变量(Z_DBS_INSTANCE, Z_DB_APP_USER, Z_DB_APP_PASS)
|
|
60
|
+
3. 连接 MySQL 数据库查询 X_APP 表获取所有应用 ID
|
|
61
|
+
4. 检查缓存目录(优先 R2MO_HOME/apps,否则 ./apps)
|
|
62
|
+
5. 识别孤立的缓存目录(缓存中存在但数据库中不存在的应用)
|
|
63
|
+
6. 使用 inquirer 询问用户确认是否删除
|
|
64
|
+
7. 递归删除确认的孤立目录
|
|
65
|
+
8. 显示详细的执行日志和统计信息(加载的环境变量、数据库连接、删除结果)
|
package/.r2mo/task/task-001.md
CHANGED
|
@@ -1,144 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
runAt: 2026-02
|
|
3
|
-
title:
|
|
2
|
+
runAt: 2026-03-02.09-48-15
|
|
3
|
+
title: 任务
|
|
4
4
|
---
|
|
5
|
-
|
|
6
|
-
# 任务
|
|
7
|
-
|
|
8
|
-
开发 `ai ex-api` 命令对已经开发好的 API 进行授权,此命令为固定命令,要完成全套执行操作,系统重启后要保证权限生效,且命令必须拥有幂等性。
|
|
9
|
-
|
|
10
|
-
### 前置条件
|
|
11
|
-
|
|
12
|
-
1. 当前项目中 `.r2mo/task/command/ex-api.yaml` 配置文件必须存在,不存在则直接退出,并且提示用户配置文件缺失。
|
|
13
|
-
2. 环境变量必须,检查这些环境变量必须存在,不存在则跳出
|
|
14
|
-
```bash
|
|
15
|
-
export Z_DB_TYPE="MYSQL" # 数据库类型: MYSQL / SQLSERVER / ORACLE / POSTGRESQL
|
|
16
|
-
export Z_DB_HOST="127.0.0.1"
|
|
17
|
-
export Z_DB_PORT="3306"
|
|
18
|
-
export Z_DBS_INSTANCE=""
|
|
19
|
-
export Z_DB_APP_USER="r2admin"
|
|
20
|
-
export Z_DB_APP_PASS="r2admin"
|
|
21
|
-
```
|
|
22
|
-
3. 环境变量检查(这个步骤必须)
|
|
23
|
-
```bash
|
|
24
|
-
export Z_APP_ID=???
|
|
25
|
-
export Z_TENANT=???
|
|
26
|
-
export Z_SIGMA=???
|
|
27
|
-
```
|
|
28
|
-
4. `ex-api.yaml` 的基本格式
|
|
29
|
-
```yaml
|
|
30
|
-
metadata:
|
|
31
|
-
identifier: "核心标识符"
|
|
32
|
-
brief: "接口描述"
|
|
33
|
-
resource: "resource.ambient"
|
|
34
|
-
level: ???
|
|
35
|
-
ptype: "权限集 S_PERM_SET 类型"
|
|
36
|
-
pname: "权限集 S_PERM_SET 名称"
|
|
37
|
-
keyword: "app.test.data"
|
|
38
|
-
target:
|
|
39
|
-
root: "ZERO_MODULE"
|
|
40
|
-
module: "ambient"
|
|
41
|
-
```
|
|
42
|
-
5. 存在 target 配置多检查一个环境变量 ZERO_MODULE,并且检查`{ZERO_MODULE}/zero-exmodule-{module}` 是否一个标准的DPA架构,若不是则命令退出
|
|
43
|
-
|
|
44
|
-
### 执行逻辑
|
|
45
|
-
|
|
46
|
-
1. 检查前置条件是否满足,不检查 `ex-api.yaml` 的格式问题
|
|
47
|
-
2. 参数检查:
|
|
48
|
-
- `-r "<METHOD> <uri>"` ,其中 `r` 的全称是 `request`
|
|
49
|
-
- `-s`,其中 `s` 的全称是 `skip`,若如此则只生成 Excel,可能最终不完美(没做去重检查),但交给开发人员处理
|
|
50
|
-
3. 定义表信息
|
|
51
|
-
- `S_RESOURCE`:资源定义
|
|
52
|
-
- `S_ACTION`:操作定义
|
|
53
|
-
- `S_PERMISSION`:权限定义
|
|
54
|
-
- `S_PERM_SET`:权限集定义
|
|
55
|
-
- `R_ROLE_PERM`:角色权限定义
|
|
56
|
-
4. 先检查 `S_ACTION` 中是否已经存在当前参数,按列名开发(`METHOD, URI`;不唯一时追加 `SIGMA, APP_ID, TENANT_ID`),只有当 `METHOD, URI` 无法提取唯一记录时追加后续三个查询条件
|
|
57
|
-
- 若存在不创建数据记录,提取 `S_ACTION` 中的 ID
|
|
58
|
-
- 若不存在则创建数据记录 / `S_RESOURCE` 需要同步创建(因为 `S_RESOURCE` 资源是新的)
|
|
59
|
-
5. 询问用户:追加新权限 / 执行已有权限,若执行已有权限,按 `S_PERMISSION` 中的 identifier 进行查询让用户选择追加到那个权限(追加到已有权限则直接跳过额外的数据库操作)
|
|
60
|
-
6. 提取数据库中已有的 `S_ROLE` 角色信息,可多选,让用户选择当前 API 授权给哪些角色
|
|
61
|
-
|
|
62
|
-
## 输出说明
|
|
63
|
-
|
|
64
|
-
### 数据库输出
|
|
65
|
-
|
|
66
|
-
- 同步五张表的信息,注意要幂等性,不引起异常
|
|
67
|
-
|
|
68
|
-
### Excel输出
|
|
69
|
-
|
|
70
|
-
1. 如果存在 `target` 的配置,则输出项目根目录应该是 `${ZERO_MODULE}/zero-exmodule-{module}`,它必须是 DPA 架构,生成的 `excel` 的信息应该位于 `${ZERO_MODULE}/zero-exmodule-{module}/zero-exmodule-{module}-domain` 的资源目录下,开发时查看一下,有一个 `plugins/***/security/RBAC_RESOURCE` 目录。
|
|
71
|
-
2. Excel 直接覆盖,不提示选择文件;文件名固化为 `identifier-method-uri.xlsx`(uri 中 `/` 转 `-`);无 target 时输出到 **-api** 项目下 `plugins/.../security/`。
|
|
72
|
-
3. `plugins/***/security/RBAC_ROLE/ADMIN.SUPER/`(固定)写入带 **falcon** 前缀的同名文件(`falcon-identifier-method-uri.xlsx`);若没有 `target` 则写入当前/ -api 项目下的 `plugins/zero-launcher-configuration/security/` 目录下。
|
|
73
|
-
4. `plugins` 都是从 maven 项目的 src/main/resources 开始计算
|
|
74
|
-
5. 在目标路径下生成对应的 `*.xlsx` 或更改现有的内容
|
|
75
|
-
|
|
76
|
-
### Excel 样式约定
|
|
77
|
-
|
|
78
|
-
- **默认模板**:模板位于 R2MO-INIT 项目固定路径 `src/_template/EXCEL/ex-api`,与执行命令所在项目无关;任意项目执行 ai ex-api 均使用该默认模板驱动。
|
|
79
|
-
- **仅填数据,样式与模板完全一致**:除写入的数据单元格 value 外,不修改任何 style(格式、颜色、边框、合并单元格、行数等)。不删行、不清格式、不填色、不设边框,输出与模板在视觉和结构上一致。
|
|
80
|
-
|
|
81
|
-
### 属性规则
|
|
82
|
-
|
|
83
|
-
- `S_RESOURCE`
|
|
84
|
-
- 资源名称,从 `ex-api.yaml` 中提取
|
|
85
|
-
- `modelRole` -> UNION 固定值
|
|
86
|
-
- 资源编码 `res` 前缀加 `S_ACTION` 的 code
|
|
87
|
-
- `identifier / type / level` 这些配置文件中有
|
|
88
|
-
- `S_ACTION`
|
|
89
|
-
- 操作编码,读取当前 `identifier` 下的核心操作编码追加 `act` 前缀
|
|
90
|
-
- 操作名称同 `brief`(和资源一样)
|
|
91
|
-
- 操作级别同 `S_RESOURCE`
|
|
92
|
-
- `S_PERMISSION`
|
|
93
|
-
- 权限名称同 `brief`(备注也想通)
|
|
94
|
-
- 权限码 `perm` 前缀
|
|
95
|
-
- 所属模型 `identifier`
|
|
96
|
-
- `S_PERM_SET`:参考前边规则提取,配置文件中有
|
|
97
|
-
- 如果出现 `keyword`,则所有编码名称直接追加,不计算,比如
|
|
98
|
-
- `res.${keyword}`
|
|
99
|
-
- `act.${keyword}`
|
|
100
|
-
- `perm.${keyword}`
|
|
101
|
-
|
|
102
|
-
### 核心输出
|
|
103
|
-
|
|
104
|
-
- 数据库中建立关系
|
|
105
|
-
- Excel中建立管理
|
|
106
|
-
- Excel中的UUID值固化
|
|
107
|
-
|
|
108
|
-
### 全局列补充(仅数据库,不写入 Excel)
|
|
109
|
-
|
|
110
|
-
仅**实体表**写入全局列:S_RESOURCE、S_ACTION、S_PERMISSION。若表中包含以下列则一并写入;Excel 中不输出这些列。关系表 R_ROLE_PERM 只有 ROLE_ID、**PERM_ID** 两列,无全局列。
|
|
111
|
-
|
|
112
|
-
| 列名 | 取值来源 |
|
|
113
|
-
|------|----------|
|
|
114
|
-
| `sigma` | 环境变量 `Z_SIGMA` |
|
|
115
|
-
| `appId` | 环境变量 `Z_APP_ID` |
|
|
116
|
-
| `tenantId` | 环境变量 `Z_TENANT` |
|
|
117
|
-
| `scope` | 环境变量 `Z_APP` |
|
|
118
|
-
| `createdBy` | 固定值 `9a0d5018-33ad-4c64-80bf-8ae7947c482f`(R2_BY) |
|
|
119
|
-
| `updatedBy` | 固定值 `9a0d5018-33ad-4c64-80bf-8ae7947c482f`(R2_BY) |
|
|
120
|
-
| `createdAt` | 当前时间(R2_NOW) |
|
|
121
|
-
| `updatedAt` | 当前时间(R2_NOW) |
|
|
122
|
-
|
|
123
|
-
- 全局列在开发时按建表固定写好,执行时不查元数据;Excel 输出中不包含上述全局列,仅做数据库同步。
|
|
124
|
-
|
|
125
|
-
### 表列信息(开发时对齐,执行时不查元数据)
|
|
126
|
-
|
|
127
|
-
**所有 SQL 与 Excel 表头列名在开发时按 RBAC 建表(如 zero-exmodule-rbac Flyway)固定写好;ai ex-api 执行时只执行 DML,不访问数据库元数据(不执行 SHOW COLUMNS 等)。**
|
|
128
|
-
|
|
129
|
-
- **R_ROLE_PERM**:关系表,仅两列 **ROLE_ID**、**PERM_ID**(无 `PERMISSION_ID`)。
|
|
130
|
-
- S_RESOURCE、S_ACTION、S_PERMISSION 实体表列名与 Flyway 一致:S_ACTION 用 **SIGMA/APP_ID/TENANT_ID**(非 Z_*),S_PERMISSION 用 **COMMENT**(非 REMARK);唯一性查询带 SIGMA 以保证幂等。全局列(SIGMA/APP_ID/TENANT_ID/CREATED_BY/UPDATED_BY/CREATED_AT/UPDATED_AT)在代码中写死,不运行时查表。
|
|
131
|
-
- 开发阶段可用 `node script/scan-rbac-schema.js`(可选 `--write`)核对库表列名与脚本一致;不修改 RBAC 的 Flyway 配置。
|
|
132
|
-
|
|
133
|
-
### 执行约定(仅 DML,不查元数据)
|
|
134
|
-
|
|
135
|
-
- ai ex-api 执行时**只执行 DML**(SELECT/INSERT/INSERT IGNORE),**不访问数据库元数据**(如 SHOW COLUMNS)。
|
|
136
|
-
- 表列名在**开发时**按 RBAC 建表(如 Flyway)固定写好,运行时不再查询表结构。
|
|
137
|
-
- **假设**:执行时数据表已存在且结构固定;**禁止**表扫描、DDL、元数据提取(已知表结构,无需运行时获取)。
|
|
138
|
-
|
|
139
|
-
### 完整打印(成功与失败)
|
|
140
|
-
|
|
141
|
-
- **启动**:配置路径、request 参数、skip、数据库连接信息。
|
|
142
|
-
- **步骤**:已存在/已创建 S_RESOURCE、S_ACTION、S_PERMISSION,R_ROLE_PERM 同步角色数,Excel 写出路径。
|
|
143
|
-
- **成功**:`[ex-api] 执行完成(幂等)` 及汇总(ACTION_ID、RESOURCE_ID、PERMISSION_ID、授权角色数、Excel 路径)。
|
|
144
|
-
- **失败**:`[ex-api] 执行失败` + 错误信息、错误码、sqlMessage、sql 语句、堆栈。
|
package/package.json
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const Ec = require("../epic");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const Ut = require("../commander-shared");
|
|
7
|
+
const inquirer = require("inquirer");
|
|
8
|
+
|
|
9
|
+
const REQUIRED_ENV_DB = ["Z_DBS_INSTANCE", "Z_DB_APP_USER", "Z_DB_APP_PASS"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 从 pom.xml 读取当前项目的 artifactId(排除 <parent> 内的)
|
|
13
|
+
*/
|
|
14
|
+
function getArtifactIdFromPom(cwd) {
|
|
15
|
+
const pomPath = path.resolve(cwd, "pom.xml");
|
|
16
|
+
if (!fs.existsSync(pomPath)) return null;
|
|
17
|
+
let content = fs.readFileSync(pomPath, "utf-8");
|
|
18
|
+
content = content.replace(/<parent>[\s\S]*?<\/parent>/i, "");
|
|
19
|
+
const m = content.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
20
|
+
return m ? m[1].trim() : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 解析 app.env:export KEY="value" 或 export KEY='value',写入 process.env
|
|
25
|
+
*/
|
|
26
|
+
function loadAppEnv(filePath) {
|
|
27
|
+
if (!fs.existsSync(filePath)) return false;
|
|
28
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
29
|
+
const lines = content.split(/\r?\n/);
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (trimmed.startsWith("#") || !trimmed.startsWith("export ")) continue;
|
|
33
|
+
const match = trimmed.match(/^export\s+([A-Za-z0-9_]+)=["']?([^"'\n]*)["']?/);
|
|
34
|
+
if (match) process.env[match[1]] = match[2].trim();
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 解析 .r2mo/app.env 路径:
|
|
41
|
+
* ONE:当前目录 .r2mo/app.env
|
|
42
|
+
* DPA:{id}-api/.r2mo/app.env,id 来自 pom.xml 或当前目录名
|
|
43
|
+
* 支持两种布局:api 在项目内 (cwd/{id}-api) 或 与项目并列 (cwd/../{id}-api)
|
|
44
|
+
*/
|
|
45
|
+
function resolveAppEnvPath(cwd) {
|
|
46
|
+
const primary = path.resolve(cwd, ".r2mo", "app.env");
|
|
47
|
+
if (fs.existsSync(primary)) return primary;
|
|
48
|
+
|
|
49
|
+
let artifactId = getArtifactIdFromPom(cwd);
|
|
50
|
+
if (!artifactId) {
|
|
51
|
+
const base = path.basename(cwd);
|
|
52
|
+
if (base && base !== ".") artifactId = base;
|
|
53
|
+
}
|
|
54
|
+
if (artifactId) {
|
|
55
|
+
const apiDir = `${artifactId}-api`;
|
|
56
|
+
const nested = path.resolve(cwd, apiDir, ".r2mo", "app.env");
|
|
57
|
+
if (fs.existsSync(nested)) return nested;
|
|
58
|
+
const sibling = path.resolve(cwd, "..", apiDir, ".r2mo", "app.env");
|
|
59
|
+
if (fs.existsSync(sibling)) return sibling;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 检查环境变量是否齐全
|
|
66
|
+
*/
|
|
67
|
+
function checkEnv(keys) {
|
|
68
|
+
const missing = keys.filter((k) => !process.env[k] || !String(process.env[k]).trim());
|
|
69
|
+
if (missing.length > 0) {
|
|
70
|
+
Ec.error("环境变量不齐,以下前置条件必须全部已设置,否则不执行。");
|
|
71
|
+
Ec.info("当前缺失的环境变量:" + missing.join(", "));
|
|
72
|
+
Ec.info("请确保以下环境变量已设置(可在 .r2mo/app.env 中 export):");
|
|
73
|
+
Ec.info(" Z_DBS_INSTANCE # 数据库实例名");
|
|
74
|
+
Ec.info(" Z_DB_APP_USER # 数据库账号");
|
|
75
|
+
Ec.info(" Z_DB_APP_PASS # 数据库密码");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取缓存目录路径
|
|
82
|
+
*/
|
|
83
|
+
function getCacheDir(cwd) {
|
|
84
|
+
const r2moHome = process.env.R2MO_HOME;
|
|
85
|
+
if (r2moHome && r2moHome.trim()) {
|
|
86
|
+
const dir = path.resolve(r2moHome.trim(), "apps");
|
|
87
|
+
Ec.info(`使用 R2MO_HOME 缓存目录:${dir}`);
|
|
88
|
+
return dir;
|
|
89
|
+
}
|
|
90
|
+
const dir = path.resolve(cwd, "apps");
|
|
91
|
+
Ec.info(`使用当前目录缓存:${dir}`);
|
|
92
|
+
return dir;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 递归删除目录
|
|
97
|
+
*/
|
|
98
|
+
function removeDir(dirPath) {
|
|
99
|
+
if (!fs.existsSync(dirPath)) return;
|
|
100
|
+
const files = fs.readdirSync(dirPath);
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
const filePath = path.join(dirPath, file);
|
|
103
|
+
const stat = fs.statSync(filePath);
|
|
104
|
+
if (stat.isDirectory()) {
|
|
105
|
+
removeDir(filePath);
|
|
106
|
+
} else {
|
|
107
|
+
fs.unlinkSync(filePath);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
fs.rmdirSync(dirPath);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = async (options) => {
|
|
114
|
+
try {
|
|
115
|
+
Ec.execute("ai ex-app:清理缓存目录中不存在于数据库的应用实例");
|
|
116
|
+
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
|
|
119
|
+
// 1. 加载 .r2mo/app.env 环境变量
|
|
120
|
+
const appEnvPath = resolveAppEnvPath(cwd);
|
|
121
|
+
if (!appEnvPath) {
|
|
122
|
+
const tried = [path.resolve(cwd, ".r2mo", "app.env")];
|
|
123
|
+
const id = getArtifactIdFromPom(cwd) || path.basename(cwd);
|
|
124
|
+
if (id) {
|
|
125
|
+
tried.push(path.resolve(cwd, `${id}-api`, ".r2mo", "app.env"));
|
|
126
|
+
tried.push(path.resolve(cwd, "..", `${id}-api`, ".r2mo", "app.env"));
|
|
127
|
+
}
|
|
128
|
+
Ec.error(".r2mo/app.env 不存在;DPA 下也未找到 {id}-api/.r2mo/app.env");
|
|
129
|
+
Ec.info("已尝试路径(id=" + (id || "未解析") + "):");
|
|
130
|
+
tried.forEach((p) => Ec.info(` - ${p}`));
|
|
131
|
+
Ec.info("请确认:1) 在项目根执行 2) 存在 .r2mo/app.env 或 {id}-api/.r2mo/app.env(嵌套或与项目并列)");
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
loadAppEnv(appEnvPath);
|
|
136
|
+
Ec.info(`✓ 已加载环境变量:${appEnvPath}`);
|
|
137
|
+
|
|
138
|
+
// 2. 检查数据库核心环境变量
|
|
139
|
+
checkEnv(REQUIRED_ENV_DB);
|
|
140
|
+
Ec.info(`✓ 环境变量检查通过:${REQUIRED_ENV_DB.join(", ")}`);
|
|
141
|
+
|
|
142
|
+
// 3. 连接数据库并查询 X_APP 表
|
|
143
|
+
const mysql = require("mysql2/promise");
|
|
144
|
+
const dbConfig = {
|
|
145
|
+
host: process.env.Z_DB_HOST || "localhost",
|
|
146
|
+
port: parseInt(process.env.Z_DB_PORT || "3306", 10),
|
|
147
|
+
user: process.env.Z_DB_APP_USER,
|
|
148
|
+
password: process.env.Z_DB_APP_PASS,
|
|
149
|
+
database: process.env.Z_DBS_INSTANCE
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
Ec.execute(`连接数据库:${dbConfig.database} @ ${dbConfig.host}:${dbConfig.port}(用户 ${dbConfig.user})`);
|
|
153
|
+
|
|
154
|
+
const conn = await mysql.createConnection(dbConfig);
|
|
155
|
+
let dbAppIds = [];
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
Ec.execute("查询 X_APP 表,提取所有应用 ID…");
|
|
159
|
+
const [rows] = await conn.execute("SELECT `id` FROM X_APP");
|
|
160
|
+
dbAppIds = rows.map((row) => String(row.id).trim()).filter((id) => id);
|
|
161
|
+
Ec.info(`✓ 数据库中共有 ${dbAppIds.length} 个应用:${dbAppIds.join(", ")}`);
|
|
162
|
+
} finally {
|
|
163
|
+
await conn.end();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 4. 检查缓存目录
|
|
167
|
+
const cacheDir = getCacheDir(cwd);
|
|
168
|
+
|
|
169
|
+
if (!fs.existsSync(cacheDir)) {
|
|
170
|
+
Ec.info(`缓存目录不存在:${cacheDir}`);
|
|
171
|
+
Ec.info("无需清理,任务完成。");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 5. 汇总缓存中不存在于数据库的应用实例
|
|
176
|
+
const cacheDirs = fs.readdirSync(cacheDir).filter((name) => {
|
|
177
|
+
const fullPath = path.join(cacheDir, name);
|
|
178
|
+
return fs.statSync(fullPath).isDirectory();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
Ec.info(`✓ 缓存目录中共有 ${cacheDirs.length} 个子目录`);
|
|
182
|
+
|
|
183
|
+
const orphanDirs = cacheDirs.filter((dirName) => !dbAppIds.includes(dirName));
|
|
184
|
+
|
|
185
|
+
if (orphanDirs.length === 0) {
|
|
186
|
+
Ec.info("✓ 缓存目录中所有应用实例均存在于数据库中,无需清理。");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Ec.info(`发现 ${orphanDirs.length} 个孤立的缓存目录(数据库中不存在):`);
|
|
191
|
+
orphanDirs.forEach((dir) => {
|
|
192
|
+
const fullPath = path.join(cacheDir, dir);
|
|
193
|
+
Ec.info(` - ${dir} (${fullPath})`);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// 6. 询问用户是否删除
|
|
197
|
+
const { confirm } = await inquirer.prompt([
|
|
198
|
+
{
|
|
199
|
+
type: "confirm",
|
|
200
|
+
name: "confirm",
|
|
201
|
+
message: `确认删除以上 ${orphanDirs.length} 个孤立的缓存目录?`,
|
|
202
|
+
default: false
|
|
203
|
+
}
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
if (!confirm) {
|
|
207
|
+
Ec.info("用户取消操作,未删除任何目录。");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 7. 删除孤立的缓存目录
|
|
212
|
+
Ec.execute(`开始删除 ${orphanDirs.length} 个孤立的缓存目录…`);
|
|
213
|
+
let deletedCount = 0;
|
|
214
|
+
let failedCount = 0;
|
|
215
|
+
|
|
216
|
+
for (const dirName of orphanDirs) {
|
|
217
|
+
const fullPath = path.join(cacheDir, dirName);
|
|
218
|
+
try {
|
|
219
|
+
removeDir(fullPath);
|
|
220
|
+
Ec.info(`✓ 已删除:${dirName}`);
|
|
221
|
+
deletedCount++;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
Ec.error(`✗ 删除失败:${dirName},错误:${err.message}`);
|
|
224
|
+
failedCount++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Ec.execute(`清理完成:成功删除 ${deletedCount} 个目录,失败 ${failedCount} 个。`);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
Ec.error("执行失败:" + err.message);
|
|
231
|
+
if (err.stack) Ec.info(err.stack);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
@@ -11,6 +11,7 @@ const executeApply = require('./fn.source.apply');
|
|
|
11
11
|
const executeExPerm = require('./fn.ex.perm');
|
|
12
12
|
const executeExApi = require('./fn.ex.api');
|
|
13
13
|
const executeExCrud = require('./fn.ex.crud');
|
|
14
|
+
const executeExApp = require('./fn.ex.app');
|
|
14
15
|
const exported = {
|
|
15
16
|
executeUuid, // ai uuid
|
|
16
17
|
executeString, // ai str
|
|
@@ -27,6 +28,7 @@ const exported = {
|
|
|
27
28
|
executeExPerm, // ai ex-perm
|
|
28
29
|
executeExApi, // ai ex-api
|
|
29
30
|
executeExCrud, // ai ex-crud
|
|
31
|
+
executeExApp, // ai ex-app
|
|
30
32
|
};
|
|
31
33
|
module.exports = exported;
|
|
32
34
|
/**
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"file-explorer": true,
|
|
3
|
-
"global-search": true,
|
|
4
|
-
"switcher": true,
|
|
5
|
-
"graph": true,
|
|
6
|
-
"backlink": true,
|
|
7
|
-
"canvas": true,
|
|
8
|
-
"outgoing-link": true,
|
|
9
|
-
"tag-pane": true,
|
|
10
|
-
"footnotes": false,
|
|
11
|
-
"properties": true,
|
|
12
|
-
"page-preview": true,
|
|
13
|
-
"daily-notes": true,
|
|
14
|
-
"templates": true,
|
|
15
|
-
"note-composer": true,
|
|
16
|
-
"command-palette": true,
|
|
17
|
-
"slash-command": false,
|
|
18
|
-
"editor-status": true,
|
|
19
|
-
"bookmarks": true,
|
|
20
|
-
"markdown-importer": true,
|
|
21
|
-
"zk-prefixer": false,
|
|
22
|
-
"random-note": false,
|
|
23
|
-
"outline": true,
|
|
24
|
-
"word-count": true,
|
|
25
|
-
"slides": true,
|
|
26
|
-
"audio-recorder": false,
|
|
27
|
-
"workspaces": true,
|
|
28
|
-
"file-recovery": true,
|
|
29
|
-
"publish": true,
|
|
30
|
-
"sync": true,
|
|
31
|
-
"bases": true,
|
|
32
|
-
"webviewer": true
|
|
33
|
-
}
|