@oj-module/cli 0.1.16 → 0.1.20

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.
Files changed (68) hide show
  1. package/devkit/README.md +11 -10
  2. package/devkit/SKILL.md +35 -18
  3. package/devkit/manual.md +359 -131
  4. package/package.json +2 -2
  5. package/shell-dist/assets/contract.js +0 -55
  6. package/shell-dist/assets/runtime.js +238 -213
  7. package/shell-dist/index.html +2 -2
  8. package/shell-dist/versions.json +5 -5
  9. package/src/build.ts +1 -0
  10. package/src/contract/__tests__/check-api.test.ts +74 -0
  11. package/src/contract/__tests__/emit-client.test.ts +132 -0
  12. package/src/contract/__tests__/emit-jsonschema-zod.test.ts +93 -0
  13. package/src/contract/__tests__/mock.test.ts +56 -0
  14. package/src/contract/__tests__/openapi-ir.test.ts +137 -0
  15. package/src/contract/__tests__/run-api.test.ts +67 -0
  16. package/src/contract/check.ts +67 -294
  17. package/src/contract/emit-client.ts +23 -15
  18. package/src/contract/emit-jsonschema-zod.ts +209 -0
  19. package/src/contract/emit-oj-schema.ts +108 -161
  20. package/src/contract/emit-stub.ts +17 -57
  21. package/src/contract/handler-scan.ts +263 -0
  22. package/src/contract/ir.ts +11 -179
  23. package/src/contract/json-schema-sample.ts +72 -0
  24. package/src/contract/mock.ts +28 -100
  25. package/src/contract/openapi-ir.ts +145 -0
  26. package/src/contract/run.ts +71 -120
  27. package/src/dev.ts +20 -18
  28. package/src/init.ts +18 -3
  29. package/src/oj.ts +6 -2
  30. package/src/vendor.ts +8 -4
  31. package/templates/api/config.yaml +1 -0
  32. package/templates/api/src/auth/api.ts +66 -0
  33. package/templates/api/src/notification/_shared/notify.ts +199 -0
  34. package/templates/api/src/notification/api.ts +231 -0
  35. package/templates/api/src/notification/del/api.ts +47 -0
  36. package/templates/api/src/notification/manifest.yaml +3 -1
  37. package/templates/api/src/notification/migrations/0001__create_notifications.sql +1 -1
  38. package/templates/api/src/notification/migrations/0002__notifications_user.sql +9 -0
  39. package/templates/api/src/notification/migrations/0003__notifications_broadcast.sql +22 -0
  40. package/templates/api/src/notification/schema.yaml +23 -1
  41. package/templates/api/src/notification/seed.sql +6 -4
  42. package/templates/api/src/personal-center/upload/api.ts +12 -1
  43. package/templates/env.d.ts +1 -1
  44. package/templates/openapi.json +471 -0
  45. package/templates/web/src/auth/__client/api.schemas.ts +8 -0
  46. package/templates/web/src/auth/__client/api.ts +110 -0
  47. package/templates/web/src/notification/__client/api.schemas.ts +50 -0
  48. package/templates/web/src/notification/__client/api.ts +186 -0
  49. package/templates/web/src/notification/entry.ts +20 -9
  50. package/templates/web/src/notifications/__client/api.schemas.ts +8 -0
  51. package/templates/web/src/notifications/__client/api.ts +80 -0
  52. package/templates/web/src/{notification/client → personal-center/__client}/api.schemas.ts +2 -14
  53. package/templates/web/src/personal-center/__client/api.ts +88 -0
  54. package/templates/web/src/web/__client/api.schemas.ts +8 -0
  55. package/templates/web/src/{notification/client → web/__client}/api.ts +12 -37
  56. package/vendor/host-versions.json +5 -5
  57. package/src/contract/emit-meta.ts +0 -175
  58. package/src/contract/emit-schema.ts +0 -133
  59. package/src/contract/evaluate.ts +0 -88
  60. package/templates/api/src/auth/login/api.ts +0 -27
  61. package/templates/api/src/auth/logout/api.ts +0 -10
  62. package/templates/api/src/auth/refresh/api.ts +0 -26
  63. package/templates/api/src/notification/contract.ts +0 -52
  64. package/templates/api/src/notification/notifications/api.ts +0 -24
  65. package/templates/api/src/notification/notifications/clear/api.ts +0 -13
  66. package/templates/api/src/notification/notifications/read/api.ts +0 -19
  67. package/templates/api/src/notification/notifications/read-all/api.ts +0 -13
  68. package/templates/api/src/personal-center/contract.ts +0 -16
package/devkit/README.md CHANGED
@@ -9,20 +9,20 @@
9
9
  > **oj v0.1.58 起站点挂载迁移**:`server.api_prefix` / `app_path` / `app_prefix` /
10
10
  > `static_sites` / `app_spa_fallback` 这五个站点键已被删除,改由顶层 **`mounts:`** 扁平表
11
11
  > 承载(一行=一个 URL 前缀+一个目录,`api`/`web` 恰好其一)。旧键出现即被 `oj serve` fail-fast 拒绝。
12
- > 仓库内 `ojm init` 模板已是 `mounts:`;oj 侧挂载、密钥信封、vars、cors、schema 校验等完整用法见
13
- > [`docs/oj-api-dev/`](../oj-api-dev/README.md)。
12
+ > `ojm init` 模板已是 `mounts:`;oj 侧挂载、密钥信封、vars、cors、schema 校验等完整用法见
13
+ > `oj-api-dev` skill 手册(`ojm init` 装机在 `.claude/skills/oj-api-dev/`)。
14
14
 
15
15
  | 文件 | 用途 |
16
16
  |---|---|
17
17
  | `SKILL.md` | Claude Code 等 agent 的 skill 入口:工作流、红线、新模块 checklist、陷阱速查,按章节号引用手册 |
18
- | `manual.md` | 模块开发手册(10 章):工程结构、写模块、契约与代码生成、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令) |
19
- | `framework-dev.md` | 框架贡献者路线(**仅本仓**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
18
+ | `manual.md` | 模块开发手册(10 章):工程结构、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令) |
19
+ | `framework-dev.md` | 框架贡献者路线(**仅框架仓库**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
20
20
 
21
21
  ## 安装(业务工程)
22
22
 
23
- **方式一(推荐):`ojm init` 自动分发。** 从 `@oj-module/cli` 某版本起,`ojm init`
24
- 会把本 devkit 自动拷进工程的 `.claude/skills/ojm-module-dev/`(与 oj 后端 devkit
25
- 一并安装,已存在则幂等跳过)。无需手动拷贝。
23
+ **方式一(推荐):`ojm init` 自动分发。** `ojm init` 会把本 devkit 自动拷进工程的
24
+ `.claude/skills/ojm-module-dev/`(与 oj 后端 devkit `oj-api-dev` 一并安装,
25
+ 已存在则幂等跳过)。无需手动拷贝。
26
26
 
27
27
  **方式二:手动拷贝**(旧工程或不走 init 时):
28
28
 
@@ -31,10 +31,11 @@
31
31
  mkdir -p .claude/skills/ojm-module-dev
32
32
  cp SKILL.md manual.md .claude/skills/ojm-module-dev/
33
33
 
34
- # 后端 API 开发另装 oj 自带的 devkit(独立 skill,随 oj 二进制分发,不在此目录内)。
35
- # 仓库里也同步了一份权威副本在 docs/oj-api-dev/(随 oj 版本更新,覆盖时以 oj 源为准):
34
+ # 后端 API 开发另装 oj 自带的 devkit(独立 skill,不在此目录内):
35
+ # 权威源随 oj 二进制分发——本机安装在 ~/.local/share/oj/devkit/,
36
+ # 工程内在 bin/devkit/(ojm vendor / init 安装 oj 时一并落盘)
36
37
  mkdir -p .claude/skills/oj-api-dev
37
- cp ../oj-api-dev/SKILL.md ../oj-api-dev/api-manual.md .claude/skills/oj-api-dev/
38
+ cp ~/.local/share/oj/devkit/SKILL.md ~/.local/share/oj/devkit/api-manual.md .claude/skills/oj-api-dev/
38
39
  ```
39
40
 
40
41
  安装后 Claude Code 里 `/ojm-module-dev` 触发,或直接说「用 ojm-module-dev 加个 xxx 模块」。
package/devkit/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ojm-module-dev
3
- description: 用 ojm 开发前后端一体工程时使用——新增/修改前端模块(entry.ts、pages、locales)、写契约 contract.ts 与 oj handler、跑 ojm api 生成 client、注册 provider/布局/插槽、改 web.config.ts,或排查「模块没加载 / 注册不生效 / 请求未绑定 / 铃铛按钮灰」时。触发场景:建模块、加页面、加接口、接管通知或上传、换布局、ojm dev 起不来。
3
+ description: 用 ojm 开发前后端一体工程时使用——新增/修改前端模块(entry.ts、pages、locales)、写 oj handler 的 .schema 与业务逻辑、跑 oj openapi + ojm api 生成 client、注册 provider/布局/插槽、改 web.config.ts,或排查「模块没加载 / 注册不生效 / 请求未绑定 / 铃铛按钮灰」时。触发场景:建模块、加页面、加接口、接管通知或上传、换布局、ojm dev 起不来。
4
4
  ---
5
5
 
6
6
  # ojm 模块开发
@@ -9,19 +9,20 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
9
9
 
10
10
  后端 handler 本身的写法(`json` 信封、`db` 查询、`manifest.yaml`、`oj test`)、
11
11
  站点挂载(`mounts`)、密钥信封(`secrets`)、vars、cors、schema 校验、broker/tasks/oidc/ws 等
12
- 属于 **`oj-api-dev` skill** 的范围——仓库内同步副本在 [`docs/oj-api-dev/`](../oj-api-dev/README.md),
12
+ 属于 **`oj-api-dev` skill** 的范围——`ojm init` 已把它装到工程的
13
+ `.claude/skills/oj-api-dev/`(权威源随 oj 二进制分发,随 oj 版本更新),
13
14
  本 skill 只讲它到前端这一侧的对接。
14
15
 
15
16
  ## 工作流
16
17
 
17
- 1. **读章**:建模块 → 手册 §2;加接口(契约→handler→生成→对接)→ §3;
18
+ 1. **读章**:建模块 → 手册 §2;加接口(handler .schema → oj openapi → 生成 → 对接)→ §3;
18
19
  接管通知/上传/菜单 → §4;换布局或加插槽 → §5;排查 → §8。
19
20
  2. **建目录**:前端 `web/src/<模块名>/`(`entry.ts` 必备,`pages/`、`locales/` 按需);
20
- 后端 `api/src/<模块名>/`(`contract.ts` + 目录镜像路由的 `api.ts`)。
21
+ 后端 `api/src/<模块名>/`(目录镜像路由的 `api.ts`,动词挂 `.schema`)。
21
22
  `entry.ts` 是模块元数据**唯一来源**——没有单独的 `package.json` / `meta.json`。
22
23
  3. **对接四步流水线**(加接口就背这条):
23
24
  ```
24
- 写契约 contract.ts → 写 handler api.ts → ojm api 生成 client → entry.ts 的 onInit 里 create<Module>Client(ctx) + 注册
25
+ 写 handler .schema → 写 handler 业务逻辑(api.ts) → oj openapi + ojm api 生成 client → entry.ts 的 onInit 里 create<Module>Client(ctx) + 注册
25
26
  ```
26
27
  4. **登记**:往工程根 `web.config.ts` 的 `modules` 数组加一项。**漏了这步模块静默不加载、全程不报错。**
27
28
  5. **验证**:`pnpm dev`(前端 devServer **5174** + oj 后端 **9778**,端口以 `api/config.yaml` 为准)
@@ -42,13 +43,15 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
42
43
  - **只创建自己模块的 client**:A 模块的 onInit 里创建 B 模块的 client 会 DEV 告警。要别家接口,
43
44
  走「B 自己注册 provider、A 消费 provider」的正路。
44
45
  - **既然接管就整套接管**:`ctx.register.*Provider` 的方法一个都不能少,不做「只接管读、写还用框架的」。
45
- - **生成物勿手改**:`web/src/<模块>/client/**`、`routes.json`、`openapi.yaml` 重跑即覆盖
46
- (eslint 已忽略)。改需求 = 改契约,重跑 `ojm api`。唯一例外是 `api/**/api.ts` stub——
47
- 文件头带 `// ojm-api:stub` 指纹的 stub 被人工动过后,工具**永不写永不删**。
48
- - **契约前缀约束**:uni-dev 形态下每个端点的 `apiPrefix` 必须**字面等于**后端模块
49
- 目录名(`api/src/order/contract.ts` → `/order`,**不是** `/api/order`),不符即人话报错。
50
- - **不返回数据的接口不写 `data`**:`data: z.null()` 过不了类型白名单,直接省略该字段。
51
- - **上传文件用契约的 `form`,不要绕过 client 手写请求**:`form: { fields: z.object({...}), files: [{ name: "avatar", required: true }] }`;`form` 与 `body` 互斥,文件部件只写 `name`/`required`/`multiple`(二进制不进 zod),`content-type` 由运行时按 boundary 自动补。
46
+ - **生成物勿手改**:`web/src/<模块>/__client/**` 重跑即覆盖(eslint 已忽略)。改需求 = 改 handler 的
47
+ `.schema`,重跑 `oj openapi` + `ojm api`。`openapi.json` 由 `oj openapi` 生成(oj 内省 `.schema` 的权威产物)。
48
+ 唯一例外是 `api/**/api.ts` stub——文件头带 `// ojm-api:stub` 指纹的 stub 被人工动过后,工具**永不写永不删**。
49
+ - **前缀约束**:API 前缀由**后端模块目录名**推导(`api/src/order/**` → `/order`,**不是** `/api/order`)。
50
+ handler `.schema` / 路由须落在该目录内,否则 oj 路由表与生成物对不上、人话报错。
51
+ - **不返回数据的接口不写 `.response`**:平级声明位直接省略即可(oj v0.1.72+;旧 `.schema.response` 通道仍兼容);校验一律放后端。
52
+ - **上传文件**:当前 `ojm api` 的 openapi 流水线不生成 form 通道的前端 client(文件部件不进 JSON Schema),
53
+ 惯例走 antd `Upload` **直连**后端(不经 JSON client),handler 用 `http.files` / `http.file(i)` 读字节,
54
+ 仅平级声明 `.response`。不要手写 `content-type`(运行时按 boundary 自动补)。
52
55
  - **前端模块的 import 只有三类**:`@oj-module/runtime`、宿主 importmap 提供的共享依赖、
53
56
  自身相对路径。共享依赖不进工程 `dependencies`(宿主提供单例,模块不得打包副本)。
54
57
  - **后端方法名是 `del`,不是 `delete`**(写成 `delete` 返回 405)。
@@ -73,10 +76,19 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
73
76
  - [ ] `web.config.ts` 的 `modules` 里已登记且 `enabled: true`
74
77
  - [ ] 路由 `handle.layout` 选对了(有页面用 `"container"`;无页面模块 `routes: []`)
75
78
  - [ ] 菜单标题用命名空间语法 `"<模块名>:menu.<key>"`,且 `locales/<lang>.json` 里有这个 key
76
- - [ ] 有接口 → 契约写了 `apiPrefix` + `route` → `ojm api` 跑过 → onInit 里 `create<Module>Client(ctx)`
79
+ - [ ] 有接口 → handler 动词挂了 `.schema`(入参)+ 平级 `.response`(出参,oj v0.1.72+)→ `oj openapi` + `ojm api` 跑过 → onInit 里 `create<Module>Client(ctx)`
77
80
  - [ ] 接管了某功能 → provider 方法数与类型定义**一个不差**
78
81
  - [ ] `pnpm dev` 起来后按 §8 五步排查全过
79
82
 
83
+ ## 旧工程迁移(contract.ts / defineApi → 新流水线)
84
+
85
+ 存量工程用 `api/src/<模块>/contract.ts`(`defineApi` + zod)声明契约——翻译层已删除,
86
+ `ojm api` 不再读取它。迁移五步:① 盘点端点 → ② 入参译到 `.schema`、`data` 译到平级
87
+ `.response`(zod→JSON Schema 对照表)→ ③ `oj openapi -c api/config.yaml -d api/src -b /api -o openapi.json`
88
+ → ④ `ojm api` + 前端调用点改 `camelCase(operationId)` → ⑤ 删 `contract.ts` 与旧产物
89
+ (per-module `routes.json` / `openapi.yaml`),`ojm api --check` 零漂移。
90
+ 前置:oj ≥ 0.1.73、框架包 ≥ 0.1.20。完整字段映射与丢失面清单见 **manual.md §3.10**。
91
+
80
92
  ## 常见陷阱速查
81
93
 
82
94
  | 症状 | 原因 | 处置 |
@@ -87,11 +99,11 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
87
99
  | 铃铛按钮全灰 | 没注册通知 provider,或只写了「拉列表」一个方法 | 4 个方法补齐 |
88
100
  | 调用报「请求未绑定」 | onInit 里没调 `create<Module>Client(ctx)` | onInit 里先 create 再发请求 |
89
101
  | 控制台「模块 A 正在为 /b 路径创建 client(越界)」 | A 创建了 B 的 client | 由 B 创建并通过 provider 提供能力 |
90
- | `ojm api` 报「schema 超出白名单」 | 契约里写了 `z.null()` / transform / refine | 不返回数据就别写 `data`;校验放后端 |
91
- | 上传接口报 400「文件字段缺失」 | 文件部件名与后端 `http.files[].field` 不一致 | 契约 `form.files[].name` 对齐后端读的字段名 |
92
- | 上传报「Unsupported Media Type」/ 后端收不到文件 | 手写了 `content-type`(boundary 丢失) | 删掉手写的 header,交运行时按 FormData 自动补 |
93
- | `ojm api` 报「apiPrefix 与目录名不符」 | 契约要求 apiPrefix 字面等于后端目录名 | 改 `apiPrefix` 或移动契约目录 |
94
- | 改了契约前端类型没变 | 没重新生成 | 跑 `ojm api`;生成是确定的,diff 应只含你改的部分 |
102
+ | `ojm api` 报「schema 超出白名单」 | `.schema` 写了白名单外关键字(如 `$ref`/`oneOf`/`format`)或非法类型 | 校验放后端;如需 union 用类型数组 `["string","number"]` |
103
+ | 上传报 400「文件字段缺失」 | 后端 `http.files` 字段名与前端 FormData 不一致 | 对照 handler 读的 `http.files[0].name` / `http.body.<name>`,字段名对齐 |
104
+ | 上传报「Unsupported Media Type」/ 后端收不到文件 | 手写了 `content-type`(boundary 丢失) | 走 antd Upload 直连,删掉手写的 header,交运行时按 FormData 自动补 |
105
+ | `oj openapi` / `ojm api` 报路由与目录不符 | handler 路径 / 组名与模块目录约定不一致 | 后端路由 = 目录镜像(manual §1.3),嵌套组 / 动词键遵循 manual §3.9 |
106
+ | 改了 `.schema` 前端类型没变 | 没重新生成 | 跑 `oj openapi` + `ojm api`;生成是确定的,diff 应只含你改的部分 |
95
107
  | 上传报 401 | `headers` 写成了固定对象,token 过期 | 写成方法,用的时候现取 |
96
108
  | 后端菜单用了我的布局名,页面却没壳 | 注册该布局的模块没加载 | 看控制台 `[layout] 未知名布局` 提醒 |
97
109
  | 后端下发的组件找不到 | 下发的组件名必须在框架页面目录里 | 自己模块的页面走模块 `routes`,别从后端下发 |
@@ -110,6 +122,11 @@ description: 用 ojm 开发前后端一体工程时使用——新增/修改前
110
122
  | 通知铃 404 `no route matched` | 缺 root 级 `/api/notifications` | 补 `api/src/notifications` 模块 |
111
123
  | `oj serve` 启动即退,报 `config uses removed server.* site keys (v0.1.58): api_prefix` | 用了 oj v0.1.58 起删除的旧站点键(`api_prefix`/`app_path`/`app_prefix`/`static_sites`/`app_spa_fallback`) | 改 `server.api_prefix: /api` 为顶层 `mounts: [{prefix: "/api", api: "src"}]`;迁移完整 YAML 由报错给出 |
112
124
  | 改完 `mounts:` 仍全 404 | `api:` 指向目录不是 oj 启动目录下的 `src` | `mounts[].api` 写 `"src"`,`-c` 指向 `api/config.yaml`(ojm dev/preview 已内置) |
125
+ | `oj serve` 启动即退,报 `module '_shared' missing manifest.yaml` | 把共享后端代码放到了顶层 `api/src/_shared/`(oj 把 `api/src` 顶层目录都当模块,缺 `manifest.yaml`) | 嵌进所属模块目录:`api/src/<模块>/_shared/`,相对导入 |
126
+ | 后端 `import '@oj-module/runtime/...'` 报 `escapes project root` | oj 解析裸 specifier 以 `api/` 为项目根并钳制不越界,`@oj-module/runtime` 符号链接在 `packages/runtime`(项目根外) | 后端原语/类型放 `api/src` 内相对导入;跨模块共享类型仍以 runtime 为单一真源,两端形状手动同步 |
127
+ | 跨用户删/读他人通知不报错却像没生效 | 原语对不存在/非本人的行 `UPDATE 0` 行返回 `ok`,没区分归属 | 原语(`delNotice`/`markRead`)返回 `boolean`/`count`,端点据 `false`/`0` 映射 `json.fail(403, "forbidden")` |
128
+ | handler 写了动词子目录(`read/api.ts` 这类) | 旧形态(oj v0.1.64 前) | 按 manual §3.9「DDD 判据速查」reshape:同聚合多动词收模块根嵌套组,`del` 保留键例外 |
129
+ | 页签怎么点都关不掉、无报错提示 | `beforeClose` 守卫 fail-safe 否决(守卫抛错按取消处理) | 先查控制台 `[tabs] beforeClose` 报错;确认弹窗逻辑与 `resolve(true/false)` 收口(manual §6.4) |
113
130
 
114
131
  ## 手册
115
132