@oj-module/cli 0.2.8 → 0.2.15
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/devkit/README.md +70 -24
- package/devkit/framework-dev.md +14 -5
- package/devkit/manual.md +69 -6
- package/devkit/module-dev-and-cdn-playground.md +197 -0
- package/devkit/sql-builder-missing-capabilities-pr.md +38 -4
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +262 -45
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +64 -0
- package/src/__tests__/versions.test.ts +26 -0
- package/src/build.ts +9 -0
- package/src/bundle.ts +8 -5
- package/src/contract/__tests__/market-client.integration.test.ts +13 -10
- package/src/index.ts +6 -0
- package/src/init.ts +22 -0
- package/src/lifecycle/__tests__/bundle.test.ts +3 -3
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/en-US.json +2 -1
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/zh-CN.json +2 -1
- package/src/lifecycle/__tests__/manifest-contract.test.ts +70 -1
- package/src/manifest-contract.ts +25 -2
- package/src/market/__tests__/bootstrap.test.ts +204 -0
- package/src/market/__tests__/build-index.test.ts +154 -0
- package/src/market/__tests__/cdn-layout.test.ts +96 -0
- package/src/market/__tests__/failure-playbook.test.ts +33 -0
- package/src/market/__tests__/fixture.test.ts +48 -0
- package/src/market/__tests__/publish.test.ts +109 -0
- package/src/market/__tests__/report.test.ts +51 -0
- package/src/market/__tests__/setup.test.ts +114 -0
- package/src/market/__tests__/verify.test.ts +111 -0
- package/src/market/bootstrap.ts +255 -0
- package/src/market/build-index.ts +159 -0
- package/src/market/cdn-layout.ts +122 -0
- package/src/market/command.ts +139 -0
- package/src/market/failure-playbook.ts +57 -0
- package/src/market/fixture.ts +138 -0
- package/src/market/index-builder.ts +76 -0
- package/src/market/publish.ts +156 -0
- package/src/market/report.ts +67 -0
- package/src/market/scan.ts +99 -0
- package/src/market/setup.ts +144 -0
- package/src/market/trial.ts +121 -0
- package/src/market/verify.ts +298 -0
- package/src/market/walkthrough.ts +506 -0
- package/src/oj.ts +3 -1
- package/src/preview-config.ts +14 -0
- package/src/preview.ts +4 -3
- package/src/sign.ts +21 -6
- package/src/usage.ts +19 -0
- package/src/vendor.ts +4 -2
- package/src/versions.ts +28 -0
- package/templates/api/.ojm-api-exempt.json +6 -2
- package/templates/api/config.yaml +20 -0
- package/templates/api/src/_platform/_lifecycle/steps/extract.ts +8 -8
- package/templates/api/src/_platform/_lifecycle/uninstall.ts +21 -53
- package/templates/api/src/_platform/_shared/service/domain/contacts.ts +15 -10
- package/templates/api/src/_platform/_shared/service/domain/solution-perms.ts +8 -6
- package/templates/api/src/_platform/_shared/service/infra/oplog.ts +16 -23
- package/templates/api/src/_platform/_shared/service/infra/query.ts +18 -0
- package/templates/api/src/_platform/_shared/service/infra/rate.ts +4 -1
- package/templates/api/src/_platform/fixtures/l1-test-user.sql +13 -0
- package/templates/api/src/_platform/manifest.yaml +1 -0
- package/templates/api/src/_platform/migrations/0013__users_profile.sql +4 -0
- package/templates/api/src/_platform/schema.yaml +12 -0
- package/templates/api/src/auth/_shared/session.ts +2 -2
- package/templates/api/src/demo/todos/api.ts +8 -9
- package/templates/api/src/notification/_shared/notify.ts +64 -26
- package/templates/api/src/notification/api.ts +1 -1
- package/templates/api/src/notification/manifest.yaml +1 -0
- package/templates/api/src/notification/migrations/0004__notifications_broadcast_hidden.sql +12 -0
- package/templates/api/src/notification/schema.yaml +7 -0
- package/templates/api/src/notification/seed.sql +5 -4
- package/templates/api/src/personal-center/profile/api.ts +63 -0
- package/templates/api/src/web/user-info/api.ts +3 -1
- package/templates/api/tests/fixture.ts +8 -16
- package/templates/api/tests/l1-builder.test.ts +91 -0
- package/templates/api/tests/lifecycle/installer.spec.ts +1 -4
- package/templates/api/tests/lifecycle/market-api.spec.ts +1 -3
- package/templates/api/tests/lifecycle/steps-fs.spec.ts +5 -8
- package/templates/api/tests/lifecycle/uninstall.spec.ts +28 -88
- package/templates/api/tests/query-utils.spec.ts +85 -1
- package/templates/api/tests/rbac-settings.spec.ts +64 -19
- package/templates/cdn/README.md +48 -0
- package/templates/global.d.ts +36 -2
- package/templates/init.include.yaml +3 -2
- package/templates/openapi.json +55 -10
- package/templates/web/src/notification/entry.ts +46 -9
- package/templates/web/src/notification/locales/en-US.json +19 -0
- package/templates/web/src/notification/locales/zh-CN.json +19 -0
- package/templates/web/src/notification/pages/center/index.tsx +131 -0
- package/templates/web/src/oj-market/entry.ts +8 -2
- package/templates/web/src/personal-center/__client/api.schemas.ts +9 -0
- package/templates/web/src/personal-center/__client/api.ts +25 -1
- package/templates/web/src/personal-center/entry.ts +5 -0
- package/templates/web/src/personal-center/pages/my-profile/index.tsx +25 -3
- package/templates/web/src/system/entry.ts +2 -1
- package/templates/web.config.ts +10 -7
- package/vendor/host-versions.json +3 -3
- package/templates/api/src/notifications/api.ts +0 -29
- package/templates/api/src/notifications/manifest.yaml +0 -5
- package/templates/web/src/notifications/__client/api.schemas.ts +0 -8
- package/templates/web/src/notifications/__client/api.ts +0 -80
package/devkit/README.md
CHANGED
|
@@ -1,31 +1,67 @@
|
|
|
1
1
|
# oj-module DevKit——模块开发手册 + agent skill
|
|
2
2
|
|
|
3
3
|
面向用 `ojm` 开发前后端一体工程的开发者与 AI agent。本目录是**源文件**,
|
|
4
|
-
发布时由 cli 的 `prepack`(`packages/cli/scripts/sync-devkit.mjs
|
|
5
|
-
`packages/cli/devkit/`,随 `@oj-module/cli`
|
|
4
|
+
发布时由 cli 的 `prepack`(`packages/cli/scripts/sync-devkit.mjs`)把其中
|
|
5
|
+
**ojm 侧的 5 个文件**同步进 `packages/cli/devkit/`,随 `@oj-module/cli`
|
|
6
|
+
发布,再由 `ojm init` 分发到工程内。
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
> devkit(`oj-api-dev` skill)在工程里的落点,由 `ojm vendor` / `ojm init` 装 oj 时一并落盘。
|
|
8
|
+
整棵树覆盖前后端两侧,但仍是**两套独立分发的 skill**(同名不同物,别混):
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
| 侧 | 本仓位置 | 用户工程里的落点 | 分发链 |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| ojm 模块开发(前端模块、契约、前后端对接) | 本目录顶层 5 个文件 | `.claude/skills/ojm-module-dev/` | `@oj-module/cli` 包 → `ojm init` 第 4b 步 |
|
|
13
|
+
| oj API 开发(后端 handler、全局对象、配置、测试) | `oj-api-dev/` 子目录(快照) | `.claude/skills/oj-api-dev/` | **oj 二进制自带**(`bin/devkit/`)→ `ojm init` 第 4 步 |
|
|
14
|
+
|
|
15
|
+
> **`oj-api-dev/` 子目录是快照,不是本仓维护的文档**:它与本机 oj 的
|
|
16
|
+
> `~/.local/share/oj/devkit/`(oj release 下载)逐字节一致,改它没有意义——
|
|
17
|
+
> 下次刷新即被覆盖。权威源在 oj 侧;它进本仓只为给框架贡献者一个
|
|
18
|
+
> 可 grep 的后端手册副本(后端细节以 oj 发布版为准)。
|
|
19
|
+
> 该子目录**不随 cli 包发货**(用户工程从 oj 二进制拿),`sync-devkit.mjs`
|
|
20
|
+
> 只同步顶层文件。
|
|
21
|
+
|
|
22
|
+
## ojm 侧文件(随 cli 包发货)
|
|
23
|
+
|
|
24
|
+
| 文件 | 用途 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `SKILL.md` | Claude Code 等 agent 的 skill 入口:工作流、红线、新模块 checklist、陷阱速查,按章节号引用手册 |
|
|
27
|
+
| `manual.md` | 模块开发手册(12 章):工程结构(含 §1.7 handler 四红线——oj 运行时实测钉出,后端必读)、写模块、契约与代码生成(含 §3.10 旧 contract.ts 工程迁移指南)、provider 接管(覆盖框架内置功能)、布局、i18n、构建发布、排查、CLI 子命令参考(ojm 原生命令 vs oj 透传命令)、方案包打包与分发、签名与信任 |
|
|
28
|
+
| `framework-dev.md` | 框架贡献者路线(**仅框架仓库**):改 `packages/runtime`、`packages/cli` 时的入口与红线 |
|
|
29
|
+
| `module-dev-and-cdn-playground.md` | 教学文档:开发态 ≠ 分发态、「怎么安装自己的模块 / 会不会重复」、本地 CDN 市场链路五步跑通(setup → publish → build-index → verify → 安装) |
|
|
30
|
+
| `sql-builder-missing-capabilities-pr.md` | 构造器能力缺口 PR 参考(**仅框架仓库**):7 项诉求的闭环记录 |
|
|
31
|
+
|
|
32
|
+
后端 handler 本身的写法(`json` 信封、`db` 查询、`manifest.yaml`、`oj test`)、
|
|
33
|
+
站点挂载(`mounts`)、密钥信封(`secrets`)、vars、cors、schema 校验、broker/tasks/oidc/ws 等
|
|
34
|
+
属于 **`oj-api-dev` skill** 的范围——`ojm init` 已把它装到工程的
|
|
35
|
+
`.claude/skills/oj-api-dev/`(权威源随 oj 二进制分发,随 oj 版本更新,
|
|
36
|
+
本仓副本见上表)。
|
|
12
37
|
|
|
13
38
|
> **oj v0.1.58 起站点挂载迁移**:`server.api_prefix` / `app_path` / `app_prefix` /
|
|
14
39
|
> `static_sites` / `app_spa_fallback` 这五个站点键已被删除,改由顶层 **`mounts:`** 扁平表
|
|
15
40
|
> 承载(一行=一个 URL 前缀+一个目录,`api`/`web` 恰好其一)。旧键出现即被 `oj serve` fail-fast 拒绝。
|
|
16
41
|
> `ojm init` 模板已是 `mounts:`;oj 侧挂载、密钥信封、vars、cors、schema 校验等完整用法见
|
|
17
|
-
> `oj-api-dev` skill
|
|
42
|
+
> `oj-api-dev` skill 手册。
|
|
43
|
+
|
|
44
|
+
## oj-api-dev 侧文件(快照,仅本仓参考)
|
|
18
45
|
|
|
19
46
|
| 文件 | 用途 |
|
|
20
47
|
|---|---|
|
|
21
|
-
| `SKILL.md` |
|
|
22
|
-
| `manual.md` |
|
|
23
|
-
| `
|
|
48
|
+
| `SKILL.md` | oj 侧 agent skill 入口(工作流、红线、checklist、陷阱速查) |
|
|
49
|
+
| `api-manual.md` | 后端开发手册(13 章):模块约定、全局对象 API、鉴权租户、测试、配置、构建发布 |
|
|
50
|
+
| `scenarios.md` | 场景速查(照抄就能跑) |
|
|
51
|
+
| `sql-builder-guide.md` | SQL 构造器专题指引(v0.1.82) |
|
|
52
|
+
| `oj-test-guide.md` | `oj test` L1 测试专题指引 |
|
|
53
|
+
| `global.d.ts` / `oj-modules.d.ts` | handler 全局对象 TS 类型 + `#` 导入别名 ambient 兜底 |
|
|
54
|
+
| `oidc-implementation.md` / `oidc-integration.md` | OIDC 演示走读与集成说明 |
|
|
55
|
+
| `README.md` | oj 侧发布索引 + **v0.1.66→v0.1.82 逐版落地记录**(查「某个能力哪版有的」看这里) |
|
|
56
|
+
|
|
57
|
+
> 注意 `oj-api-dev/README.md` 里「由仓库 `docs/devkit/` 经 `cargo xtask build`
|
|
58
|
+
> 归置到 `bin/devkit/`」说的是**上游 oj 仓**的结构,不是本仓——本仓的
|
|
59
|
+
> `docs/devkit/` 是 ojm 侧 devkit(你正在读的这个目录)。
|
|
24
60
|
|
|
25
61
|
## 安装(业务工程)
|
|
26
62
|
|
|
27
|
-
**方式一(推荐):`ojm init` 自动分发。** `ojm init`
|
|
28
|
-
`.claude/skills
|
|
63
|
+
**方式一(推荐):`ojm init` 自动分发。** `ojm init` 把两侧 skill 都装进工程的
|
|
64
|
+
`.claude/skills/`(ojm 侧来自 cli 包;oj 侧来自 oj 二进制的 `bin/devkit/`,
|
|
29
65
|
已存在则幂等跳过)。无需手动拷贝。
|
|
30
66
|
|
|
31
67
|
**方式二:手动拷贝**(旧工程或不走 init 时):
|
|
@@ -35,33 +71,43 @@
|
|
|
35
71
|
mkdir -p .claude/skills/ojm-module-dev
|
|
36
72
|
cp SKILL.md manual.md README.md .claude/skills/ojm-module-dev/
|
|
37
73
|
|
|
38
|
-
# 后端 API 开发另装 oj 自带的 devkit(独立 skill
|
|
74
|
+
# 后端 API 开发另装 oj 自带的 devkit(独立 skill):
|
|
39
75
|
# 权威源随 oj 二进制分发——本机安装在 ~/.local/share/oj/devkit/,
|
|
40
76
|
# 工程内在 bin/devkit/(ojm vendor / init 安装 oj 时一并落盘)
|
|
41
77
|
mkdir -p .claude/skills/oj-api-dev
|
|
42
78
|
cp ~/.local/share/oj/devkit/SKILL.md ~/.local/share/oj/devkit/api-manual.md .claude/skills/oj-api-dev/
|
|
43
79
|
```
|
|
44
80
|
|
|
45
|
-
安装后 Claude Code 里 `/ojm-module-dev`
|
|
81
|
+
安装后 Claude Code 里 `/ojm-module-dev` 或 `/oj-api-dev` 触发,
|
|
82
|
+
或直接说「用 ojm-module-dev 加个 xxx 模块」。
|
|
46
83
|
|
|
47
84
|
## 与 `ojm init` 的关系
|
|
48
85
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
86
|
+
两条分发链都接在 `packages/cli/src/init.ts`:
|
|
87
|
+
|
|
88
|
+
- **第 4 步**:从用户 `bin/devkit/`(oj release 下载)拷 `SKILL.md` /
|
|
89
|
+
`api-manual.md` + 有则拷的 `sql-builder-guide.md` / `oj-test-guide.md` +
|
|
90
|
+
`global.d.ts` → `.claude/skills/oj-api-dev/`;
|
|
91
|
+
- **第 4b 步**:从 cli 包内置 `devkit/`(prepack 由本目录顶层同步)拷
|
|
92
|
+
`SKILL.md` / `manual.md` / `README.md` → `.claude/skills/ojm-module-dev/`。
|
|
52
93
|
|
|
53
94
|
```mermaid
|
|
54
95
|
flowchart LR
|
|
55
|
-
SRC["docs/devkit
|
|
96
|
+
SRC["docs/devkit/ 顶层(ojm 侧源文件,只在此处维护)"] -->|"cli prepack:sync-devkit.mjs(只同步顶层,不含 oj-api-dev/)"| PKG["packages/cli/devkit/(发布副本,随 @oj-module/cli)"]
|
|
56
97
|
PKG -->|"ojm init 第 4b 步(幂等跳过已存在)"| PROJ[".claude/skills/ojm-module-dev/"]
|
|
57
|
-
OJBIN["bin/devkit/(oj
|
|
98
|
+
OJBIN["bin/devkit/(oj 二进制自带)"] -->|"ojm init 第 4 步"| API[".claude/skills/oj-api-dev/"]
|
|
99
|
+
OJBIN -.->|"本仓快照刷新:整目录覆盖(保持逐字节一致)"| SNAP["docs/devkit/oj-api-dev/(仅本仓参考,不发货)"]
|
|
58
100
|
```
|
|
59
101
|
|
|
60
102
|
## 更新
|
|
61
103
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
104
|
+
- **ojm 侧**:手册与 skill 随 `@oj-module/cli` 版本一起发布;升级 cli 后重跑
|
|
105
|
+
`ojm init` 即可刷新(幂等跳过已存在的文件——若想强制覆盖旧拷贝,先删
|
|
106
|
+
`.claude/skills/ojm-module-dev/` 再 init)。源文件只在仓库 `docs/devkit/` 顶层一处维护。
|
|
107
|
+
- **oj 侧**:手册与 skill 随 oj 版本发布;升级 oj 后用新 `bin/devkit/` 覆盖旧拷贝。
|
|
108
|
+
框架贡献者要把本仓快照同步到新版:从 `~/.local/share/oj/devkit/` 整目录覆盖
|
|
109
|
+
`docs/devkit/oj-api-dev/`(10 个文件,保持逐字节一致以便对账)。
|
|
65
110
|
|
|
66
|
-
>
|
|
67
|
-
>
|
|
111
|
+
> 本目录顶层 5 个文件是**自包含**的:业务工程拿到即可读懂,不需要工程仓库里的
|
|
112
|
+
> 其它文档。`oj-api-dev/` 子目录是 oj 侧快照,同样自包含(其自身 README 即索引),
|
|
113
|
+
> 但不随 cli 包发货、仅本仓参考。维护者视角的内容来源整理在 `framework-dev.md`(仅本仓)。
|
package/devkit/framework-dev.md
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
| 端到端可重复验证流程 | `docs/prd/framework-verification-playbook.md` |
|
|
16
16
|
| 某特性的设计与评审记录 | `docs/prd/<日期>-<特性>-design.md` |
|
|
17
17
|
| 各阶段实战记录(骨架→注入→认证→CRUD→release) | `apps/playground-oj/docs/phase*.md` |
|
|
18
|
-
| 后端 handler 写法 | `
|
|
18
|
+
| 后端 handler 写法 | `docs/devkit/oj-api-dev/api-manual.md`(`oj-api-dev` skill 的本仓快照;用户工程侧在 `.claude/skills/oj-api-dev/`,随 oj 二进制分发) |
|
|
19
19
|
|
|
20
20
|
> ⚠️ 那两份 guide 有**已知陈旧处**,以源码为准:
|
|
21
21
|
> dev 端口实际是 **3333**(guide 写的 5173 已过时;注意与 `ojm dev` 的前端 devServer
|
|
22
22
|
> **5174** 不是一回事——前者是本仓 vite,后者是业务工程 devServer);包版本以
|
|
23
|
-
> `packages/*/package.json`
|
|
23
|
+
> `packages/*/package.json` 为准(撰写时实际 **0.2.8**,仅供参考,以当前 package.json 为准)。
|
|
24
24
|
|
|
25
25
|
## §2 两包结构与依赖方向
|
|
26
26
|
|
|
@@ -133,7 +133,7 @@ pnpm test tests/shell # 宿主全量
|
|
|
133
133
|
**一键发布**:`node scripts/publish-packages.mjs`(lockstep 门禁 → 构建 → 守卫测试 →
|
|
134
134
|
runtime → cli → registry 复核;`--dry-run` 只演练)。下面的手动步骤是它的展开参考。
|
|
135
135
|
|
|
136
|
-
- **两包**同版本号(`@oj-module/runtime` + `@oj-module/cli`,当前 `0.2.
|
|
136
|
+
- **两包**同版本号(`@oj-module/runtime` + `@oj-module/cli`,当前 `0.2.8`),后续发版保持
|
|
137
137
|
lockstep;`@plane/kit` / `@plane/editor` 不在发布集内(脚本里已注释)。
|
|
138
138
|
- **必须 `pnpm publish`**——用 `npm publish` 会漏出 `workspace:*` / `catalog:` 字面量。
|
|
139
139
|
- **顺序:先 `runtime` 后 `cli`**。`runtime` 未公开时,新 `cli` 的精确依赖会悬空,
|
|
@@ -167,7 +167,13 @@ runtime → cli → registry 复核;`--dry-run` 只演练)。下面的手动
|
|
|
167
167
|
`README.md`,已存在则幂等跳过)。
|
|
168
168
|
|
|
169
169
|
`packages/cli/devkit/` 是发布副本、由 prepack 生成,已加进 `.gitignore` 不入库;
|
|
170
|
-
|
|
170
|
+
源仍是仓库 `docs/devkit/` 顶层,改文档只动那里一处。
|
|
171
|
+
|
|
172
|
+
> `docs/devkit/oj-api-dev/` 是 **oj 侧 devkit 的快照**(10 个文件,与
|
|
173
|
+
> `~/.local/share/oj/devkit/` 逐字节一致),仅供本仓 grep 后端手册,
|
|
174
|
+
> **不随 cli 包发货**(`sync-devkit.mjs` 只同步顶层文件)——用户工程的
|
|
175
|
+
> `oj-api-dev` skill 由 oj 二进制分发。升级本机 oj 后要同步快照:
|
|
176
|
+
> 从 `~/.local/share/oj/devkit/` 整目录覆盖该子目录。
|
|
171
177
|
|
|
172
178
|
> 历史记录:此处原为「待对接(未做)」,上述两步落地后即完成。
|
|
173
179
|
|
|
@@ -175,7 +181,10 @@ runtime → cli → registry 复核;`--dry-run` 只演练)。下面的手动
|
|
|
175
181
|
|
|
176
182
|
## 附:本 devkit 内容来源(维护者看)
|
|
177
183
|
|
|
178
|
-
`docs/devkit/`
|
|
184
|
+
`docs/devkit/` 是**自包含**的交付文档(顶层 5 个文件,含
|
|
185
|
+
`sql-builder-missing-capabilities-pr.md` 这份构造器能力缺口 PR 参考),业务工程拿到即可读懂,不依赖工程仓库里的其它文档。
|
|
186
|
+
另有 `oj-api-dev/` 子目录是 oj 侧 devkit 的快照(10 文件,仅本仓参考、不发货),
|
|
187
|
+
结构说明见 `README.md`。
|
|
179
188
|
下面的来源仅用于维护时追溯与同步,不参与交付:
|
|
180
189
|
|
|
181
190
|
| 源(本仓其它位置) | 贡献到 devkit 的内容 |
|
package/devkit/manual.md
CHANGED
|
@@ -100,7 +100,9 @@ api/src/<模块>/<路径...>/api.ts → /api/<模块>/<路径...>
|
|
|
100
100
|
| `api/src/<模块>/manifest.yaml` | `tables: [表名]` | **与 `schema.yaml` 双向一致**(约定),只写一边启动即失败 |
|
|
101
101
|
|
|
102
102
|
> 少了 migration → 表根本建不出来;少了 `schema.yaml` / `manifest.tables`
|
|
103
|
-
> →
|
|
103
|
+
> → 启动归属校验失败,且任何经 `db.table("<表名>")` 访问该表的代码都会被 oj 的 DB 构造器以
|
|
104
|
+
> `unknown table` 拒绝(2026-10-19 演练实踩:仅由迁移建表、未声明的 `sys_rate_window` /
|
|
105
|
+
> `sys_login_throttle` 导致登录恒 500);两边列定义漂移 → reconcile 报差异(`oj schema diff` 可见)。
|
|
104
106
|
|
|
105
107
|
### 1.6 迁移与 seed 的应用时机
|
|
106
108
|
|
|
@@ -214,7 +216,7 @@ export default defineModule({
|
|
|
214
216
|
path: "/books",
|
|
215
217
|
handle: {
|
|
216
218
|
layout: "container", // ⚠️ 布局级路由必填(见 §5.1)
|
|
217
|
-
order: 5, //
|
|
219
|
+
order: 5, // 菜单排序(≥0 主区升序;<0 入侧边栏底部固定区)
|
|
218
220
|
title: "books:menu.books", // 命名空间语法 "<模块名>:<key>"
|
|
219
221
|
icon: createElement(BookOutlined),
|
|
220
222
|
},
|
|
@@ -251,7 +253,7 @@ export default defineModule({
|
|
|
251
253
|
| 字段 | 说明 |
|
|
252
254
|
|---|---|
|
|
253
255
|
| `layout` | `"container"`(完整壳)\| `"parent"`(嵌套父级)\| `"fullscreen"`(全屏)\| `"none"`(默认,裸渲染)\| 自定义名(§5) |
|
|
254
|
-
| `order` |
|
|
256
|
+
| `order` | 菜单排序值(双段序轴):`≥0` 主菜单区升序;`<0` 底部固定区(pin 在侧边栏底部,区内仍按 order 升序);未声明按登记下标兜底;同 order 按根 path 字母序 |
|
|
255
257
|
| `title` | 菜单标题,用 `"<模块名>:menu.<key>"` |
|
|
256
258
|
| `icon` | `createElement(XxxOutlined)` |
|
|
257
259
|
| `keepAlive` | 页面缓存(**必须挂在 `layout: "container"` 下才生效**) |
|
|
@@ -297,7 +299,7 @@ export default defineModule({
|
|
|
297
299
|
可服务的同源路径)。直接 `import "vditor/dist/..."` 其它深路径会命中前缀 external 但无
|
|
298
300
|
importmap 键,构建期报错——需要新深路径请联系框架方在共享表登记。
|
|
299
301
|
|
|
300
|
-
### 2.7 本版本共享依赖钉版(0.2.
|
|
302
|
+
### 2.7 本版本共享依赖钉版(0.2.8 快照)
|
|
301
303
|
|
|
302
304
|
宿主经 importmap 提供以下单例(模块 `devDependencies` 须对齐,供 `tsc` 用);完整 68 项矩阵以
|
|
303
305
|
`pnpm exec ojm info` 或 `packages/cli/shell-dist/versions.json`(构建期生成)为准。
|
|
@@ -326,7 +328,7 @@ importmap 键,构建期报错——需要新深路径请联系框架方在共
|
|
|
326
328
|
| `pinyin-pro` | 3.29.3 |
|
|
327
329
|
| `vditor` | 4.0.0 |
|
|
328
330
|
|
|
329
|
-
框架包版本:`@oj-module/runtime` / `@oj-module/cli` 为 `0.2.
|
|
331
|
+
框架包版本:`@oj-module/runtime` / `@oj-module/cli` 为 `0.2.8`(**双包 lockstep**);
|
|
330
332
|
`@plane/kit` / `@plane/editor` 是独立版本线(当前 `0.1.20`,随宿主 importmap 提供,
|
|
331
333
|
不参与 lockstep)。`oj` 二进制最低版本门禁 `MIN_OJ_VERSION = 0.1.73`——契约流水线要求
|
|
332
334
|
oj ≥ 0.1.73:平级 `.response` 出参声明位自 v0.1.72 起、`oj openapi` 的 `del`→`delete`
|
|
@@ -913,7 +915,8 @@ ctx.register.routesApi({
|
|
|
913
915
|
```ts
|
|
914
916
|
export default {
|
|
915
917
|
async get() {
|
|
916
|
-
|
|
918
|
+
// 表须按 §1.5 三件套在 schema.yaml + manifest.yaml 声明;db.table() 构造器要求表已声明
|
|
919
|
+
const rows = await db.table("menus").select(["path", "title", "icon"]).orderBy([{ field: "sort" }]).all();
|
|
917
920
|
json.ok(rows.map(r => ({
|
|
918
921
|
path: String(r.path),
|
|
919
922
|
handle: { layout: "container", title: String(r.title) },
|
|
@@ -1739,6 +1742,65 @@ ojm trust revoke <key_id> # 吊销:阻断新装/升级,不动已装
|
|
|
1739
1742
|
|
|
1740
1743
|
---
|
|
1741
1744
|
|
|
1745
|
+
## §13 方案菜单归组规则
|
|
1746
|
+
|
|
1747
|
+
> 适用:写方案(前后端一体模块)时让路由菜单按模块自动分组。归组是**纯视觉层**——
|
|
1748
|
+
> 开发态(`ojm dev` 直接扫源码)不归组;装进宿主且方案路由行就绪后才归组,开发形态不变。
|
|
1749
|
+
|
|
1750
|
+
### 13.1 归组规则
|
|
1751
|
+
|
|
1752
|
+
方案路由分 `main` / `admin` / `hidden` 三类。归组只动 `main` 和 `admin`:
|
|
1753
|
+
|
|
1754
|
+
- **`main` 路由 ≥ 2 条(同模块)** → 收进一个合成父菜单:
|
|
1755
|
+
- 父项 key:`solgroup:main:<模块名>`
|
|
1756
|
+
- 父项标题:裸 i18n key `<方案名>:menu.group`(翻译归渲染层,本仓只存裸 key)
|
|
1757
|
+
- 子项按各自 `order` 升序;只有 1 条 `main` 路由时不归组,原样平铺
|
|
1758
|
+
- **`admin` 路由** → 挂进宿主底部「系统设置」(`/system`)菜单的 children 末尾,同样按模块
|
|
1759
|
+
归成 `solgroup:admin:<模块名>` 父项
|
|
1760
|
+
- 若宿主没有 `/system`(极少数自定义宿主):`admin` 路由不归组、回落平铺,并告警一次
|
|
1761
|
+
(warnOnce,同消息不重复)
|
|
1762
|
+
|
|
1763
|
+
> `/system` 是框架侧断言,不是方案 manifest 能声明或验证的。方案只需把 `admin` 路由标
|
|
1764
|
+
> `category: "admin"`,挂载点交给框架。
|
|
1765
|
+
|
|
1766
|
+
### 13.2 组标题 i18n key 约定(最容易踩的坑)
|
|
1767
|
+
|
|
1768
|
+
归组父项标题是 `<方案名>:menu.group`(`<方案名>` = 你的方案 manifest 的 `name`)。这条 key
|
|
1769
|
+
**必须**存在于双语 locale 文件,且必须是**嵌套**结构:
|
|
1770
|
+
|
|
1771
|
+
```jsonc
|
|
1772
|
+
// web/src/<模块>/locales/zh-CN.json
|
|
1773
|
+
{ "menu": { "group": "Wiki" } } // 命中 <方案名>:menu.group
|
|
1774
|
+
|
|
1775
|
+
// web/src/<模块>/locales/en-US.json
|
|
1776
|
+
{ "menu": { "group": "Wiki" } }
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
- 只声明一条就够:无论多少模块、多少 `main`/`admin` 路由,框架只认一个 `<方案名>:menu.group`(按 name 去重)
|
|
1780
|
+
- 必须嵌套在 `menu.group`,不能平铺成 `"<方案名>:menu.group": "..."`(i18next 点路径取值)
|
|
1781
|
+
- 缺这条 key → 构建期契约校验直接 fail(构建期 fail-fast)
|
|
1782
|
+
|
|
1783
|
+
### 13.3 开发者的该做 / 别做
|
|
1784
|
+
|
|
1785
|
+
该做(DO):
|
|
1786
|
+
|
|
1787
|
+
- 路由 `handle.title` 写裸 key:`{ title: "wiki:menu.space", order: 200, layout: "container" }`
|
|
1788
|
+
- manifest `routes[]` 每条标 `category`(main/admin/hidden)+ `order` 整数 + `title_key`
|
|
1789
|
+
(main/admin 必填,须匹配 `<方案名>:menu.*`)
|
|
1790
|
+
- 在 `locales/{zh-CN,en-US}.json` 里补 `menu.group`
|
|
1791
|
+
- 多个 `main` 路由想归到一组 → 让它们 `module` 字段相同、`category: "main"`
|
|
1792
|
+
- `admin` 路由统一 `category: "admin"`,框架自动塞进 `/system`
|
|
1793
|
+
|
|
1794
|
+
别做(DON'T):
|
|
1795
|
+
|
|
1796
|
+
- 为归组父项写路由 / 配 handle —— 父项是合成的,没有真实路由
|
|
1797
|
+
- 直接写中文 `label` / `title` —— 必须走 i18n key,否则切语言不动
|
|
1798
|
+
- 在方案里假设 `/system` 的 key 或结构 —— 那是框架断言,自定义宿主会回落平铺
|
|
1799
|
+
- 给隐藏(hidden)或禁用(`enabled: false`)路由指望归组 —— 它们不参与
|
|
1800
|
+
- 一个模块只挂 1 条 `main` 路由还想要父菜单 —— 规则门槛是 ≥2,单条保持平铺
|
|
1801
|
+
|
|
1802
|
+
---
|
|
1803
|
+
|
|
1742
1804
|
## 附录:章节目录
|
|
1743
1805
|
|
|
1744
1806
|
| 章 | 内容 | 什么时候读 |
|
|
@@ -1755,3 +1817,4 @@ ojm trust revoke <key_id> # 吊销:阻断新装/升级,不动已装
|
|
|
1755
1817
|
| §10 CLI 子命令参考 | ojm 原生命令 vs oj 透传命令 | 查命令用法 |
|
|
1756
1818
|
| §11 方案包:打包与分发 | build --bundle、Form A/B、分发源、索引规范 | 发布方案包 |
|
|
1757
1819
|
| §12 签名与信任 | keygen/sign/install 发布流程、信任库、TOFU、bootstrap key | 首次签名发布;管理信任 |
|
|
1820
|
+
| §13 方案菜单归组规则 | main ≥2 按模块归父菜单、admin 挂进系统设置、组标题 i18n key `<方案名>:menu.group` 约定、开发者 DO/DON'T | 写方案要让菜单按模块分组 |
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# 开发态模块与本地 CDN 演练场
|
|
2
|
+
|
|
3
|
+
> 一篇写给「第一次在本工程开发模块」的人的教学文档。读完之后你应该能回答三个问题:
|
|
4
|
+
> **① 开发中的模块要不要「安装」?② 要的话装到哪、会不会重复?③ 怎么在本地把市场链路完整跑一遍?**
|
|
5
|
+
> 适用版本:`@oj-module/cli` 0.2.12+(`ojm market` 子命令),运行期 `oj` ≥ 0.1.73。
|
|
6
|
+
|
|
7
|
+
## 1. 先建立心智模型:开发态 ≠ 分发态
|
|
8
|
+
|
|
9
|
+
整个体系里存在**两套互不相干**的模块生命周期,混在一起是绝大多数困惑的来源:
|
|
10
|
+
|
|
11
|
+
| | 开发态 | 分发态 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 模块在哪 | 工程内源码:`web/src/A` + `api/src/A` | 一个 `.tgz` 包(`ojm build --bundle` 产出) |
|
|
14
|
+
| 谁登记它 | `web.config.ts` 的 `modules` 列表 → `ojm build` 生成 `modules.json` | 安装器往目标站点 upsert |
|
|
15
|
+
| 跑起来靠 | `ojm dev` 直跑源码,改一行刷新即见 | 安装器十阶段(下载→验签→解压→迁移→注册→热更→冒烟) |
|
|
16
|
+
| 典型场景 | 你在写它 | 别人(或另一个站点)装它 |
|
|
17
|
+
|
|
18
|
+
一句话:**开发态没有「安装」这个动作**。`ojm dev` 起的站点直接扫描 `api/src` 产生路由、读 `modules.json` 装配菜单——你写的模块 A 此刻「已经在跑」,只是还没打过包。
|
|
19
|
+
|
|
20
|
+
## 2. 常见问题:我开发了模块 A,怎么「安装」我自己?
|
|
21
|
+
|
|
22
|
+
### 2.1 正确答案:装到别处,不是装进你正在开发的工程
|
|
23
|
+
|
|
24
|
+
「安装自己」听起来矛盾,是因为安装这个动作的语义是**把一个包的分发产物并入一个宿主站点**。你正在开发的工程就是 A 的家,再装一遍相当于把家具搬进它已经在的屋子。
|
|
25
|
+
|
|
26
|
+
真正想验证「A 这个包能不能装、安装流程对不对」时,有三个去处,按推荐顺序:
|
|
27
|
+
|
|
28
|
+
1. **一次性隔离站点**:`ojm market trial --fixture <name>@<version>`(随机端口 + 一次性 DB + 一次性密钥 + 隔离信任库/CDN),跑完即毁,不碰你的工程、DB、信任库。这是唯一「装了也不心疼」的地方,也是最快的「这包能不能装」判据(实测 11 段全 ok 约 12 秒)。
|
|
29
|
+
2. **另一个工程**:`ojm install dist/A-0.1.0.tgz`(file 源,multipart 直传)。
|
|
30
|
+
3. **市场链路**:`ojm market publish` 落 CDN → 市场页点安装(§4 全套)。
|
|
31
|
+
|
|
32
|
+
### 2.2 偏要在本工程试怎么办
|
|
33
|
+
|
|
34
|
+
先 `ojm uninstall A` 再装,且升级走安装器的重装路径、不要手工覆盖同版本文件。理由见 §3.3 的「遮蔽」。
|
|
35
|
+
|
|
36
|
+
## 3. 常见问题:装完之后路由、菜单会重复吗?
|
|
37
|
+
|
|
38
|
+
**不会重复——注册面全是 upsert(按键覆盖),不是 insert(追加)。** 但有两个坑,分开说。
|
|
39
|
+
|
|
40
|
+
### 3.1 upsert 语义:同键只有一份
|
|
41
|
+
|
|
42
|
+
| 面 | 键 | 重复安装时发生什么 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| 路由 `sys_solution_route` | `path` | 同 path 覆写元数据、下架标记复位 |
|
|
45
|
+
| 角色 / 权限 | 模块 + code | 按键 upsert;**本次未声明**的条目标记下架(行保留、不删) |
|
|
46
|
+
| 系统设置 `sys_setting` | `key` | 按 key upsert,但**管理员改过的值永不复活** |
|
|
47
|
+
| `modules.json` | 模块名 | 按模块名登记,唯一条目 |
|
|
48
|
+
|
|
49
|
+
所以「菜单出现两条 A」这种事不会发生——菜单条目按模块名唯一。
|
|
50
|
+
|
|
51
|
+
### 3.2 但同名再装会被直接拒
|
|
52
|
+
|
|
53
|
+
安装前置校验有**方案名唯一**约束:同名方案已装 → 拒装并报占用者版本(`方案名已被已装方案「A@x.y.z」占用`)。只有升级 / 重装路径放行。这是第一道防线:从入口就不给你制造重复的机会。
|
|
54
|
+
|
|
55
|
+
### 3.3 真正的坑:两个来源抢同一个 name/path(遮蔽)
|
|
56
|
+
|
|
57
|
+
注意 §3.1 的 upsert 是**按 name/path** 而不是按来源。于是存在这种状态:
|
|
58
|
+
|
|
59
|
+
> 你源码里的 A(`api/src/A`,路由由 oj 扫描源码产生)和某次安装产物 `dist/A-0.1.0/` 同时存在。
|
|
60
|
+
|
|
61
|
+
表现是:路由与模块条目各自只有一份(不重复),但**哪一份生效取决于挂载命中顺序**——症状为「我改了源码却不生效」或「行为怎么都不对」。这是本主题下最难查的一类问题:没有报错、没有重复,只是「被另一个自己顶替了」。
|
|
62
|
+
|
|
63
|
+
**规避方式**:别让两个来源同时在场。日常开发不装;试装去隔离站点;本工程试过之后 `ojm uninstall A` 清干净再回到源码直跑。
|
|
64
|
+
|
|
65
|
+
## 4. 本地 CDN 演练场:把市场链路完整跑一遍
|
|
66
|
+
|
|
67
|
+
模板自带一个**本地模拟 CDN**(工程根 `cdn/` + `api/config.yaml` 的 `/cdn` 挂载),配上 `ojm market` 四条命令,就能在本地复刻「发包 → 上架 → 索引 → 安装」全链路。不需要外部服务。
|
|
68
|
+
|
|
69
|
+
### 4.1 全流程(五步)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# 0) 起站点(dev 或 serve 均可;市场地址必须指向 oj 子进程的端口,见 §5.1)
|
|
73
|
+
ojm dev
|
|
74
|
+
|
|
75
|
+
# 1) 打开市场地址:把 framework.market_cdn_url 指向 http://127.0.0.1:9778/cdn
|
|
76
|
+
# 站点管理员首次登录处于改密悬挂期时会自动改密换全权 token
|
|
77
|
+
ojm market setup --user admin --pass 123456 --new-pass <你的口令>
|
|
78
|
+
|
|
79
|
+
# 2) 发包:合成 fixture(或 --real 走 ojm build --bundle)→ 确定性 tgz → 包级签名
|
|
80
|
+
# --rebuild-index 顺带重建索引
|
|
81
|
+
ojm market publish --fixture demo-solution@0.1.0 --id <发布方id> --rebuild-index
|
|
82
|
+
|
|
83
|
+
# 3) 离线验包:不起站点、不装、不要凭据
|
|
84
|
+
ojm market verify --rebuild-index
|
|
85
|
+
|
|
86
|
+
# 4) 浏览器打开市场页 → 看到卡片 → 点安装
|
|
87
|
+
# 首次遇陌生发布方会停在「待信任」→ ojm trust confirm --task <id> → 重试即成
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
每条命令的边界:
|
|
91
|
+
|
|
92
|
+
| 命令 | 做什么 | **不**做什么 |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `market setup` | 写系统设置 `framework.market_cdn_url` | 不改写 `api/config.yaml`(挂载由模板自带) |
|
|
95
|
+
| `market publish` | 落 `<name>-<version>.tgz` + 包级 `.tgz.sig` | **不碰索引**(索引是共享单文件,多人发布会互相覆盖) |
|
|
96
|
+
| `market build-index` | 扫 `cdn/*.tgz` 提取包内 manifest,全量重建两份索引并签名 | 不落包 |
|
|
97
|
+
| `market verify` | 完整性 / 包级签 / 索引签 / manifest 结构四类校验 + 漂移检出 | 不起站点、不装、不访问任何远端 |
|
|
98
|
+
|
|
99
|
+
### 4.2 索引为什么是「扫描重建」而不是「增量合并」
|
|
100
|
+
|
|
101
|
+
因为索引签名对不上多人协作:如果 publish 时读-改-写共享索引,后发者会把前者的签名覆盖掉,且存在竞态。而索引签名在运行期只是**完整性层**(防目录被改),不是信任锚——那就干脆把它降级成派生产物:**谁都能重建,重建结果只取决于目录里有什么**。这也是 `publish` 与 `build-index` 必须分成两个命令的原因。
|
|
102
|
+
|
|
103
|
+
### 4.3 信任模型(30 秒版)
|
|
104
|
+
|
|
105
|
+
- **包级签名**(`<tgz>.sig`,Ed25519):信任锚。安装时逐字节验,不过即拒(fail-closed)。
|
|
106
|
+
- **索引签名**:完整性层。索引被改能发现,但**不背书内容安全**。
|
|
107
|
+
- **TOFU**:首次遇到未登记的发布方 key,安装停在待信任状态,管理员确认后写入信任库;首次绑定不可改。
|
|
108
|
+
- `market verify` 的结论恒带一句边界声明:**校验通过只说明来源与完整性,不构成内容安全性背书**——这不是免责套话,是信任模型的准确描述。
|
|
109
|
+
|
|
110
|
+
## 5. 两个已经踩过的坑(别再踩)
|
|
111
|
+
|
|
112
|
+
### 5.1 dev 形态下,`/cdn` 不在 dev server 上
|
|
113
|
+
|
|
114
|
+
`ojm dev` 的 Node 服务在 **5174**,只把 `/api` 反代给 oj 子进程;`/cdn` 是 **oj 的挂载**,只在 oj 子进程(`config.yaml` 的 `server.port`,默认 9778)上成立。所以市场地址**必须写绝对 URL** 且端口取自 config——写成相对 `/cdn` 的话,dev 下取到的是 index.html,报出来是难懂的 YAML 解析错,不是干净 404。`market setup` 已按 config 推导,别手改。
|
|
115
|
+
|
|
116
|
+
### 5.2 CDN 里摆了 `.tgz.sig` 侧车,不等于能装
|
|
117
|
+
|
|
118
|
+
安装器的验签段只认 `manifests.yaml` 里的 `archive.signature`。侧车文件在目录里摆着**不够**,索引不带过去就是 `sig_missing` 硬拒。`market build-index` 已负责把侧车读进索引——所以规范动作永远是 **publish → build-index**,两个都跑。
|
|
119
|
+
|
|
120
|
+
> 附注:曾有一条「包内 `api/<mod>/manifest.yaml` 的 desc 必须逐字等于方案 summary」的校验,已于 2026-10-21 移除——那是构建期映射被当成安装期契约,而 desc 只是模块描述文本、非安全边界。现存相关校验只有:`summary` 必填、`desc` 必填非空。
|
|
121
|
+
|
|
122
|
+
## 6. 实战:发一个真方案,并躲开七个坑
|
|
123
|
+
|
|
124
|
+
上面的 fixture 路径能秒出可装包,但真实交付要么写方案工程、要么至少知道真构建长什么样。下面是
|
|
125
|
+
2026-10-21 一次**完整演练**(宿主 `ojm init` → 方案工程 → 打包签名 → 发布 → 安装 → TOFU → 装成)
|
|
126
|
+
沉淀下来的路径与坑。演练两轮全绿(换全新宿主零手工补丁复跑同样通过)。
|
|
127
|
+
|
|
128
|
+
### 6.1 方案工程最小形态
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
solution-demo/
|
|
132
|
+
├── manifest.yaml # 方案契约清单(Form A)
|
|
133
|
+
├── api/src/<name>/
|
|
134
|
+
│ ├── manifest.yaml # oj 原生清单(name/desc/version/tables)
|
|
135
|
+
│ ├── schema.yaml # 表结构真源(必须与 manifest.tables 对齐)
|
|
136
|
+
│ ├── routes.js
|
|
137
|
+
│ ├── migrations/0001__init.sql
|
|
138
|
+
│ └── ping/api.ts # handler 必须是 .ts
|
|
139
|
+
└── web/src/<name>/locales/{zh-CN,en-US}.json # 引用 i18n key 时必需
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
<host>/bin/oj build -d api/src -o api/dist # 后端构建(只用宿主已装的 oj)
|
|
144
|
+
node "$CLI_PKG/bin/ojm.mjs" build --bundle # 打包(构建即校验)
|
|
145
|
+
node "$CLI_PKG/bin/ojm.mjs" keygen --id demo.publisher --out ./keys --yes
|
|
146
|
+
node "$CLI_PKG/bin/ojm.mjs" sign dist/<name>-<version>.tgz --id demo.publisher --key ./keys/private.pem --check
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 6.2 发布与安装(release 形态)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd <host>
|
|
153
|
+
node "$CLI_PKG/bin/ojm.mjs" market publish --real ../solution-demo --id demo.publisher --key ../keys/private.pem --rebuild-index
|
|
154
|
+
node "$CLI_PKG/bin/ojm.mjs" market verify --rebuild-index
|
|
155
|
+
|
|
156
|
+
node "$CLI_PKG/bin/ojm.mjs" build # 生成 api/dist + web/dist(含 api/dist/versions.json)
|
|
157
|
+
# config:platform.root 改成 dist(安装目标恒为 release 部署)
|
|
158
|
+
(cd api && ../bin/oj serve -c config.yaml -b /api --api-path dist --console-log &)
|
|
159
|
+
node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user admin --pass 123456 --new-pass <新口令>
|
|
160
|
+
# 市场页点安装 → 首次停在「待信任」→ 核对指纹 → ojm trust confirm --task <id> → 重试 → done
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 6.3 七个坑(按撞见顺序)
|
|
164
|
+
|
|
165
|
+
| # | 现象 | 根因 / 正确做法 |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| ① | `oj build` 报 `tables 声明 "x" 不在 schema.yaml` | oj 用 `schema.yaml` 做表结构真源,`manifest.tables` 只是引用面;补 `schema.yaml`(`tables: {<t>: {pk, columns}}`),与迁移语句对齐 |
|
|
168
|
+
| ② | `oj build` 成功但 `routes.js` 是空表、dist 里没有 handler | **只编译 `.ts` handler**;写成 `ping/api.js` 会被静默丢弃 → 改 `api.ts` |
|
|
169
|
+
| ③ | `build --bundle` 报 `缺少 web/src/<name>/locales/…` | manifest 一旦引用 `title_key`/`label_key` 就必须双语 locale(后端方案也要);且 key 必须**嵌套** `{"menu":{"main":"…"}}`,平铺 `"menu.main"` 查不到 |
|
|
170
|
+
| ④ | 安装 POST 返回 200,但 `market/task` 恒 404 | 宿主 config 缺 `fs:` 段 → 安装器落盘被 jail 拒、失败发生在任务文件写入前;0.2.13 起模板自带,旧工程手补 |
|
|
171
|
+
| ⑤ | 安装卡 `preflight`:`无法读取宿主 runtime 版本` | 安装器读 `api/dist/versions.json`;0.2.13 起 `ojm build` 双落点(旧版手抄 `web/dist/versions.json`) |
|
|
172
|
+
| ⑥ | 安装卡 `reloading`:`NotCapable: module '_platform' is not platform-privileged` | 宿主 config 的 `platform.modules` 未列 `_platform`(0.2.13 起模板自带) |
|
|
173
|
+
| ⑦ | 安装卡 `reloading`:`reloadConfig 返回 unchanged` / 卡 `smoke`:探活 404 | 起站形态不对:`ojm preview` 服务派生 config(安装器热更写的是 `api/config.yaml`,被监视文件没变);dev(`--api-path src`)装到 dist 的模块不被发现。**安装必须对真实 config 以 `--api-path dist` 起站** |
|
|
174
|
+
|
|
175
|
+
### 6.4 装成之后怎么确认
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
curl -s http://127.0.0.1:9778/api/<name>/ping # {"code":0,...pong...}
|
|
179
|
+
cat api/dist/manifests.yaml | grep <name> # <name>: <version>
|
|
180
|
+
ls api/dist/<name>-<version>/ # 安装产物目录
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 7. 一页速查
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
写模块 → 源码直跑(ojm dev),不要装
|
|
187
|
+
验证能装 → ojm market trial(隔离站点,12s),或 ojm install <tgz> 到另一个工程
|
|
188
|
+
重复? → 不会。注册面全 upsert;同名再装被前置校验直接拒
|
|
189
|
+
市场链路 → setup → publish → build-index → verify → 市场页安装(release 形态:--api-path dist)
|
|
190
|
+
绝对 URL → 市场地址端口取自 config(9778),不是 dev server 的 5174
|
|
191
|
+
信任 → 包级签=锚,索引签=完整性,TOFU 管陌生发布方,verify 不背书内容安全
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 8. 延伸阅读
|
|
195
|
+
|
|
196
|
+
- 方案打包与分发全貌、签名与信任:本 skill 的 `manual.md` §11–§12
|
|
197
|
+
- 后端 handler 写法、站点挂载(`mounts`)等:`oj-api-dev` skill 手册
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# 上游 PR 提案:SQL 构造器缺失能力清单
|
|
2
2
|
|
|
3
|
+
> **闭环状态(2026-10-20):7 项诉求全部闭环**——6 项随 oj 0.1.81/0.1.82 发布落地,第 4 项
|
|
4
|
+
> (orIgnore + fromSelect)能力本身已发布但**组合在 sqlite 上被上游明文拒绝**,模板按上游
|
|
5
|
+
> 文档指引保留一处 sanctioned 裸 SQL 绕行(`notify.ts:216`,api-manual §"orIgnore + fromSelect
|
|
6
|
+
> 组合在 sqlite 不支持"原文即指向该绕行)。逐项映射见 §6;模板 `api/src` 内 `保留 db.query`
|
|
7
|
+
> 注释已归零(仅剩上述 1 处 `保留 db.exec`)。
|
|
8
|
+
>
|
|
3
9
|
> 目的:把模板 `db.query` 全面迁移到 `db.table(...)` 构造器以获得多库(sqlite / mysql / postgres)可移植性时,
|
|
4
|
-
>
|
|
10
|
+
> 识别出构造器**当时无法表达**、只能回退 `db.query` 的 7 类能力。本文档作为向上游 `oj` 运行时 SQL 构造器提 PR 的依据。
|
|
5
11
|
>
|
|
6
12
|
> 证据来源:`~/.local/share/oj/devkit/api-manual.md`(查询构造器章节,含 `like` 操作清单 §1142、子查询限制 §1343、
|
|
7
13
|
> `union` §1346、CTE §1378 等);框架 `README.md` v0.1.66(ESCAPE 纪律);模板代码 `packages/cli/templates/api/src/**`。
|
|
@@ -38,9 +44,15 @@
|
|
|
38
44
|
|
|
39
45
|
### 3.1 `like` 缺 `ESCAPE` 子句(P0)
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
> **订正(2026-10-20,上游复核结论)**:本节原判「`WhereCond` 的 `like` 不接受 `escape` 字段」
|
|
48
|
+
> 是**误报**——`like` 的 `escape` 逃生舱本就存在(白名单 `! / | ~ # @`、显式拒 `\`,见
|
|
49
|
+
> devkit `sql-builder-guide.md` §3.5)。真正缺的不是逃生舱,而是**业务零转义的语义化模糊匹配**:
|
|
50
|
+
> 每个调用点手写 `%!v!%` 式转义模式易错且三方言心智负担重。上游按此落地
|
|
51
|
+
> `contains` / `startswith` / `endswith`(v0.1.81),本节诉求以更彻底的形式闭环。
|
|
52
|
+
|
|
53
|
+
**为什么曾是硬约束**:框架对模糊搜索有强制纪律——`LIKE` 元字符必须转义,且**不能用 `ESCAPE '\'`**
|
|
54
|
+
(mysql 默认 `sql_mode` 下 `ESCAPE '\'` 直接 1064)。当时判定构造器无出口,各调用点只能回退
|
|
55
|
+
`db.query` 手写 `LIKE ? ESCAPE '!'`。
|
|
44
56
|
|
|
45
57
|
**阻塞的真实代码**(`contacts.ts:20`):
|
|
46
58
|
|
|
@@ -232,3 +244,25 @@ db.table("sys_refresh_token").select("*").where({ field: "token_hash", op: "eq",
|
|
|
232
244
|
7. `session.ts:114-115` → `select("*")`。
|
|
233
245
|
|
|
234
246
|
全部关闭后,模板 `api/src` 内 `保留 db.query` / `保留 d.exec` 注释应归零,达成"零原生 SQL 可移植"目标。
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 6. 闭环状态(2026-10-20 实测)
|
|
251
|
+
|
|
252
|
+
| # | 诉求 | 上游落地(版本) | 模板现状 |
|
|
253
|
+
|---|------|------------------|----------|
|
|
254
|
+
| 1 | `like` 的 `ESCAPE` 转义子句 | `contains` / `startswith` / `endswith` 语义化模糊匹配(**v0.1.81**),引擎自动转义 `%`/`_`/`!` | 已闭环:`contacts.ts` 等模糊搜索全部走 `contains`。**订正**:原判「`like` 无 escape 出口」系误报——escape 逃生舱本就存在(白名单 `! / | ~ # @`);真正缺的是业务零转义语义 op,上游按此落地(L1 实测 `%` 元字符不泛化) |
|
|
255
|
+
| 2 | `join ... ON` 值条件 | join 支持表别名 + ON 值条件(**v0.1.81**):`{ left, op, value }` 混排列对列/列对常量 | 已闭环:`notify.ts:165-168` 广播 LEFT JOIN `r.user_id = ?` 直表达 |
|
|
256
|
+
| 3 | `select` 字面量/计算列 | 字面量列 `{ lit, as }`(**v0.1.81**,值走绑定参数);`expr` 任意表达式入口经上游四路评审**砍除**(计算列用现成 `case`) | 字面量部分已闭环:`notify.ts:163/177` 的 `'user'/'broadcast' as scope`。**计算列子项被设计化解掉**:广播分支第 4 列直接取 `r.read_time`,经 UNION 位置对齐落 `is_read` 列名,是否已读由 `mapItem` 按非空推导(`notify.ts:69-88` 注释同源说明)——模板侧无 `expr` 诉求残留 |
|
|
257
|
+
| 4 | `INSERT OR IGNORE` / `INSERT … SELECT` | `orIgnore()` 与 `fromSelect(q, cols)` 均落地(**v0.1.81**);但**组合在 sqlite 上被明文拒绝**(v0.1.82 文档:`orIgnore with fromSelect is not supported on sqlite`,报错原文即指引绕行) | 能力闭环 + 一处 sanctioned 绕行:VALUES 形态 upsert 已全量构造器化(`markRead` 等);仅 `markAllRead` 的 `INSERT OR IGNORE … SELECT`(`notify.ts:216-223`)保留裸 SQL——三方言通用的单语句,正是上游文档点名的绕行姿势 |
|
|
258
|
+
| 5 | 全表 delete 强制 where 豁免 | `truncate()`(**v0.1.82**):显式整表清空,渲染 `DELETE FROM t`,三方言通用 | 已闭环:`rate.ts` 限流重置改 `truncate()`;未采纳 `where({ __all: true })`(独立动词比魔法 where 值更不可误用) |
|
|
259
|
+
| 6 | 动态 WHERE 组合 | `whereIf(test, cond)`(**v0.1.81**):`undefined/null/false/[]` 整条跳过(注意 `""`/`0` 是合法值会保留——可选过滤的缺失值必须归一化为 `undefined`,模板 oplog 曾踩此坑并修复) | 已闭环:`demo/todos`、`uninstall.ts` 等全部改 `whereIf`;未采纳裸片段出口(whereIf 已覆盖诉求场景) |
|
|
260
|
+
| 7 | `select *` | `select(["*"])`(**v0.1.81**,必须是唯一元素) | 已闭环:`session.ts` refresh 查询改 `select(["*"])` |
|
|
261
|
+
|
|
262
|
+
**结论**:模板 `api/src` 内 `保留 db.query` 注释归零;`保留 d.exec` 仅剩 `notify.ts:216` 一处,
|
|
263
|
+
且属上游文档明文记载的 sqlite 组合限制绕行(非能力缺口)。"零原生 SQL 可移植"目标以
|
|
264
|
+
「一处上游 sanctioned 绕行」的形态达成。
|
|
265
|
+
|
|
266
|
+
> 上游侧复核记录(oj 0.1.81 release notes,devkit `oj-api-dev/README.md`):7 条诉求落地时
|
|
267
|
+
> 判定「1 条误报、1 条部分误报」——误报即 §3.1 的 escape 出口(本就存在),部分误报即 §3.3
|
|
268
|
+
> 的计算列(`expr` 被砍,`case` 已够用);另附赠表别名 `as()`(#8)。本表与该记录口径一致。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.15",
|
|
5
5
|
"description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@faker-js/faker": "^10.4.0",
|
|
52
|
-
"@oj-module/runtime": "0.2.
|
|
52
|
+
"@oj-module/runtime": "0.2.15",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|