@oj-module/cli 0.2.20 → 0.2.25
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/manual.md +100 -12
- package/devkit/module-dev-and-cdn-playground.md +10 -10
- package/package.json +4 -4
- package/shell-dist/assets/runtime.js +61 -35
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +13 -13
- package/src/__tests__/cli.test.ts +225 -0
- package/src/__tests__/init-packagejson.test.ts +29 -0
- package/src/__tests__/layout.test.ts +8 -5
- package/src/__tests__/new-scaffold.test.ts +59 -0
- package/src/__tests__/usage.test.ts +45 -47
- package/src/build.ts +4 -4
- package/src/builtin.ts +93 -0
- package/src/bundle.ts +237 -33
- package/src/cli.ts +265 -0
- package/src/contract/__tests__/market-client.integration.test.ts +12 -12
- package/src/contract/check.ts +4 -4
- package/src/dev.ts +75 -2
- package/src/index.ts +31 -191
- package/src/info.ts +49 -7
- package/src/init.ts +25 -11
- package/src/keygen.ts +0 -19
- package/src/layout.ts +11 -11
- package/src/lifecycle/__tests__/authoring.test.ts +3 -14
- package/src/lifecycle/__tests__/builtin.test.ts +97 -0
- package/src/lifecycle/__tests__/bundle.test.ts +188 -25
- package/src/lifecycle/__tests__/install.test.ts +0 -23
- package/src/lifecycle/__tests__/new.test.ts +129 -0
- package/src/lifecycle/__tests__/smoke.test.ts +4 -3
- package/src/manifest-contract.ts +16 -3
- package/src/market/__tests__/bootstrap.test.ts +24 -0
- package/src/market/__tests__/setup.test.ts +8 -1
- package/src/market/bootstrap.ts +10 -8
- package/src/market/cdn-layout.ts +3 -3
- package/src/market/command.ts +0 -23
- package/src/market/publish.ts +10 -2
- package/src/market/setup.ts +14 -2
- package/src/market/trial.ts +3 -3
- package/src/market/walkthrough.ts +7 -6
- package/src/new.ts +226 -0
- package/src/preview-config.ts +2 -2
- package/src/preview.ts +5 -4
- package/src/publish.ts +0 -22
- package/src/sign.ts +0 -22
- package/src/usage.ts +482 -198
- package/templates/README.md +3 -3
- package/templates/cdn/README.md +6 -6
- package/templates/config.yaml +5 -4
- package/templates/init.include.yaml +1 -1
- package/templates/package.json +3 -1
- package/templates/seed.mysql.sql +2 -1
- package/templates/seed.pg.sql +2 -1
- package/templates/src/_platform/api/fixtures/l1-test-user.sql +4 -5
- package/templates/src/_platform/api/market/api.ts +11 -8
- package/templates/src/_platform/api/seed.mysql.sql +11 -3
- package/templates/src/_platform/api/seed.pg.sql +11 -3
- package/templates/src/_platform/api/seed.sql +18 -6
- package/templates/src/_platform/api/solutions/api.ts +2 -2
- package/templates/src/oj-market/web/api/context.ts +2 -2
- package/templates/src/oj-market/web/components/InstallDialog.tsx +13 -6
- package/templates/src/oj-market/web/components/RollbackDialog.tsx +7 -3
- package/templates/src/oj-market/web/components/UninstallConfirm.tsx +18 -7
- package/templates/src/oj-market/web/hooks/useMarket.ts +30 -18
- package/templates/src/system/web/locales/en-US.json +1 -1
- package/templates/src/system/web/locales/zh-CN.json +1 -1
- package/templates/src/system/web/pages/users/roles.tsx +3 -1
- package/templates/tests/l1-builder.test.ts +2 -2
- package/templates/tests/lifecycle/market-api.spec.ts +35 -5
- package/templates/tests/lifecycle/verify-checks.spec.ts +1 -1
- package/templates/tests/lifecycle/verify-report.spec.ts +1 -1
- package/templates/tests/rbac-seed.spec.ts +12 -10
- package/templates/tests/seed-role-accounts.spec.ts +87 -0
- package/vendor/host-versions.json +3 -3
- package/src/args.ts +0 -122
package/devkit/manual.md
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
├── tests/ # 后端 BDD 测试(node:sqlite 内存库 + 真实迁移建表)
|
|
40
40
|
├── src/
|
|
41
41
|
│ ├── tsconfig.json # 后端 typecheck 工程(extends 上两者,exclude */web)
|
|
42
|
+
│ ├── .ojm-builtin.json # 框架自带方案名单(init 派生,可手改/删除;打包排除 + info 折叠,见 §11)
|
|
42
43
|
│ ├── styles.css
|
|
43
44
|
│ ├── _platform/ # 框架级:users 表 / rbac / 市场编排 / 生命周期
|
|
44
45
|
│ │ ├── api/ # + _shared/(respond、service/、site-facade)
|
|
@@ -74,7 +75,19 @@
|
|
|
74
75
|
> **历史布局仍被识别**:`web/src/<name>`(uni-dev)与 `web/<name>`(框架根仓)照旧可用,
|
|
75
76
|
> 只是 `ojm init` 不再产出。两套源码树**互斥**——同时存在时 `ojm` 直接报错,而不是静默选一个。
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
登录账号:**账号名=内置角色名**一一对应,口令均 `123456`,且全部**免首登改密**(must_change=0,纯开发便利,勿用于生产):
|
|
79
|
+
|
|
80
|
+
| 账号 | 角色 | 能干什么 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `superadmin` | superadmin | 全部能力(含安全组设置、成员资格管理) |
|
|
83
|
+
| `admin` | admin | 日常管理(不含 superadmin 专属项) |
|
|
84
|
+
| `editor` | editor | 页面门编辑级 |
|
|
85
|
+
| `viewer` | viewer | 页面门访客级 |
|
|
86
|
+
|
|
87
|
+
> 旧脚手架库(admin 绑 superadmin / must_change=1)**无需手工迁移**:seed 每次启动幂等重放,
|
|
88
|
+
> 自动收敛为账号名=角色名并清零 must_change(`DELETE` 旧绑定 + `UPDATE`,见 `_platform/api/seed.sql`)。
|
|
89
|
+
|
|
90
|
+
端口:前端 devServer **5174**,oj 后端 **9778**
|
|
78
91
|
(以工程根 `config.yaml` 的 `server.port` 为准)。
|
|
79
92
|
|
|
80
93
|
### 1.2 两个模块体系,同名不同物
|
|
@@ -93,6 +106,33 @@
|
|
|
93
106
|
历史布局下两侧分居 `web/src/<模块>/` 与 `api/src/<模块>/`——**同一个名字、两个目录**,
|
|
94
107
|
是布局迁移时最容易对不上的地方。
|
|
95
108
|
|
|
109
|
+
#### manifest.yaml 与 solution.yaml:两种身份,别混淆
|
|
110
|
+
|
|
111
|
+
工程里会出现两个名字里带 "manifest" 的文件,用途**完全不同**,混用会出构建/运行期怪问题:
|
|
112
|
+
|
|
113
|
+
| 文件 | 层级 | 身份 | 谁读它 | 写什么 |
|
|
114
|
+
|---|---|---|---|---|
|
|
115
|
+
| `src/<模块>/api/manifest.yaml` | **模块级**(每个后端模块一份) | oj **原生七键**清单(`name`/`desc`/`version`/`tables`/…) | **oj 运行时**——按 `manifest.yaml` 枚举发现并校验 api 后端模块 | 模块名、版本、表清单(与 `schema.yaml` 双向一致,§1.5) |
|
|
116
|
+
| `solution.yaml`(`src/<模块>/` 或工程根) | **方案级**(整个方案一份) | 方案包契约(身份 / 路由 / api 段 / web 段 / 权限 / …) | **`ojm build --bundle`**——打包入口与契约校验 | 方案名、版本、`api` 段(模块+前缀+tables+deps+anonymous)、`web` 段(entry/css/integrity)、`routes`、`health` |
|
|
117
|
+
|
|
118
|
+
关键区别与红线:
|
|
119
|
+
|
|
120
|
+
- **模块级 `manifest.yaml` 是 oj 发现 api 后端的唯一真源**:`ojm dev/build` 递归扫描
|
|
121
|
+
`src/<模块>/api/manifest.yaml` 认领后端模块;oj 启动也用它做强校验(`name ==` 目录名、版本同源)。
|
|
122
|
+
它**只能有 oj 原生七键**,写任何方案级字段(`routes` / `permissions` / …)都会被拒启。
|
|
123
|
+
- **方案级契约以前也叫 `manifest.yaml`(工程根)**,但它会和上面的「oj 按 `manifest.yaml` 枚举 api 模块」
|
|
124
|
+
撞名:**工程根再放一份 `manifest.yaml` 当 bundle 契约,会被 oj 的搜索枚举误当成 api 模块**,污染后端发现。
|
|
125
|
+
所以解决方案统一改名为 **`solution.yaml`**,`ojm build --bundle` 优先认它(`manifest.yaml` 仅作旧方案回退)——
|
|
126
|
+
**后端按 `manifest.yaml` 枚举 api、打包按 `solution.yaml` 取契约**,两条枚举互不干扰。
|
|
127
|
+
**位置**:单模块方案推荐放 `src/<模块>/solution.yaml`(契约与所打包模块同位);`ojm build --bundle`
|
|
128
|
+
也兼容工程根 `solution.yaml`,以及两处的 `manifest.yaml` 回退。**模块级契约只认 `src/<模块>/` 根下、
|
|
129
|
+
绝不进 `<模块>/api/`**——否则照样会和 oj 的 api 枚举撞车。
|
|
130
|
+
- 进包后文件名固定回到 `manifest.yaml`(安装侧只认这个名字):`ojm build --bundle` 把工程根或
|
|
131
|
+
`src/<模块>/` 下的 `solution.yaml`(或旧 `manifest.yaml`)拷贝进 tgz 内的 `<name>-<version>/manifest.yaml`,安装器/市场仍按老名字消费。
|
|
132
|
+
|
|
133
|
+
> 一句话:**`manifest.yaml` 是给 oj 认模块用的(模块级、七键、oj 枚举);`solution.yaml` 是给 `ojm build --bundle`
|
|
134
|
+
> 打包用的(方案级、工程根)。两者文件名不同、层级不同、读者不同,别写错层。**
|
|
135
|
+
|
|
96
136
|
### 1.3 后端路由 = 目录镜像
|
|
97
137
|
|
|
98
138
|
```
|
|
@@ -1377,12 +1417,15 @@ useEffect(() => {
|
|
|
1377
1417
|
pnpm dev # 开发:devServer + oj 后端(src/<模块>/api 保存即热更)
|
|
1378
1418
|
# 交互快捷键(仅 TTY 终端启用,类 Vite):
|
|
1379
1419
|
# r 强制重建并热更新 h 显示帮助 c 清屏 o 浏览器打开 q 退出(oj 一并终止)
|
|
1420
|
+
# 提示行常驻终端最末行(滚动区),日志在其上方滚动不会被顶走(ojm ≥ 0.2.22)
|
|
1380
1421
|
# Ctrl+C(SIGINT)/ kill(SIGTERM)终止 dev 时,前端与后端 oj 一起终止:
|
|
1381
1422
|
# 先关 SSE 通道 → 停 oj 子进程 → 打印「后端 oj 已随前端一起终止」,不留 oj 孤儿占端口
|
|
1382
1423
|
pnpm build # = ojm build:oj build(生成 routes.js)+ 前端全站合并到 web/dist
|
|
1383
1424
|
pnpm preview # = ojm preview:oj migrate(verify 闸)→ oj serve 单服务(API + 静态 SPA 同进程)
|
|
1384
1425
|
pnpm typecheck # = tsc --noEmit(前端工程)
|
|
1385
1426
|
pnpm typecheck:api # = tsc --noEmit -p src/tsconfig.json(后端工程,见 §6.5)
|
|
1427
|
+
pnpm new # = ojm new <name>:脚手架内生成用户方案骨架(见 §10.1)
|
|
1428
|
+
pnpm bundle # = ojm build --bundle:打包全部用户方案(自带方案自动排除,见 §11)
|
|
1386
1429
|
pnpm exec ojm info # 版本矩阵 + 模块清单(报障用)
|
|
1387
1430
|
pnpm exec ojm vendor # 下载/重装 oj 二进制
|
|
1388
1431
|
```
|
|
@@ -1616,6 +1659,28 @@ export default async (path: string, html: string) => {
|
|
|
1616
1659
|
> release 下只有 `routes.js` 里的路由存在,目录镜像路由不生效——所以参数路由要先
|
|
1617
1660
|
> `ojm build` 再测(见 §7)。
|
|
1618
1661
|
|
|
1662
|
+
#### `ojm build --bundle [name]`
|
|
1663
|
+
|
|
1664
|
+
打包**方案包**(solution bundle,可分发产物;语义细节见 §11)。只组装不构建,先 `ojm build` 再打包。
|
|
1665
|
+
|
|
1666
|
+
- `--bundle` 无参:工程根有 `solution.yaml` 则只打根契约(旧单方案行为零变化);
|
|
1667
|
+
否则收集 `src/*/` 下全部**用户方案**契约逐个打包(框架自带方案自动排除,日志注明「框架自带,跳过」)。
|
|
1668
|
+
- `--bundle <name>` / `--bundle=<name>`:只打 `src/<name>/solution.yaml` 这一份(manifest.yaml 回退)。
|
|
1669
|
+
- 两段式:全部契约校验通过才开始写盘;零用户方案时报错指路 `ojm new`。
|
|
1670
|
+
|
|
1671
|
+
#### `ojm new <name> [--layout container|none] [--api]`
|
|
1672
|
+
|
|
1673
|
+
脚手架内生成一份用户方案骨架(module-root 布局):`src/<name>/solution.yaml` +
|
|
1674
|
+
`web/{entry.ts,pages/,locales/}`(`--api` 再加 `api/{manifest.yaml,api.ts}`)。
|
|
1675
|
+
生成即被扫描收录,补产物后 `ojm build --bundle <name>` 直接可打(round-trip)。
|
|
1676
|
+
非 module-root 布局拒绝(会先要求迁移);名字撞内置方案 / 保留名 / 已存在目录都会拒绝。
|
|
1677
|
+
```bash
|
|
1678
|
+
ojm new crm --layout container # 建 src/crm/ 骨架
|
|
1679
|
+
pnpm dev # 看效果
|
|
1680
|
+
ojm build --bundle crm # 打包
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
|
|
1619
1684
|
#### `ojm preview [port]`
|
|
1620
1685
|
|
|
1621
1686
|
生产形态预览(设计 §6,2026-10-19 单服务化):
|
|
@@ -1647,9 +1712,11 @@ export default async (path: string, html: string) => {
|
|
|
1647
1712
|
|
|
1648
1713
|
生成物重跑即覆盖(eslint 已忽略);唯一例外是带 `// ojm-api:stub` 指纹的 stub,人碰过则工具永不写删。
|
|
1649
1714
|
|
|
1650
|
-
#### `ojm info`
|
|
1715
|
+
#### `ojm info [--all]`
|
|
1651
1716
|
|
|
1652
1717
|
输出版本矩阵(`@oj-module/cli` / `@oj-module/runtime` / 钉版 `@plane/*` / `oj` 二进制)与模块清单。
|
|
1718
|
+
module-root 下数据源是「扫描 + `web.config.ts` 登记」并集(未登记的用户模块也可见)。
|
|
1719
|
+
框架自带方案默认折叠为一行计数(`--all` 展开平铺)。
|
|
1653
1720
|
**报障用**:拿不准「工程里到底装了什么版本、哪些模块被登记」时,先 `ojm info` 贴出来。
|
|
1654
1721
|
|
|
1655
1722
|
#### `ojm vendor [tag] [--force]`
|
|
@@ -1697,20 +1764,22 @@ export default async (path: string, html: string) => {
|
|
|
1697
1764
|
### 10.3 本地市场链路:`ojm market` 子命令
|
|
1698
1765
|
|
|
1699
1766
|
`market` 是一组**本地模拟 CDN + 市场链路**工具,专供作者在本机验证「方案包能不能发、能不能装」,
|
|
1700
|
-
**不触碰真实生产 CDN**。所有产物落在工程根 `cdn/`(由 `config.yaml` 的 `/cdn`
|
|
1701
|
-
|
|
1767
|
+
**不触碰真实生产 CDN**。所有产物落在工程根 `cdn/`(由 `config.yaml` 的 `/cdn` 挂载直出)。
|
|
1768
|
+
**脚手架 seed 的 `framework.market_cdn_url` 缺省即相对路径 `/cdn`(本站点自供),起站即市场可用,
|
|
1769
|
+
无需 setup**;只有指向外部真 CDN 时才需要 `market setup` 改写设置。
|
|
1702
1770
|
|
|
1703
|
-
>
|
|
1771
|
+
> `market setup` 已非必需(见上);它只剩「指向外部真 CDN」一个场景。
|
|
1704
1772
|
> 想看「这个包到底能不能装」而不污染工程,用 `ojm market trial`(一次性隔离站点,跑完即毁)。
|
|
1705
1773
|
> 每个子命令都能 `ojm market <sub> --help` 单独看帮助。
|
|
1706
1774
|
|
|
1707
1775
|
| 子命令 | 一句话 |
|
|
1708
1776
|
|---|---|
|
|
1709
|
-
| `ojm market setup [--url <站点>] [--user <u> --pass <p>] [--new-pass <p>]` |
|
|
1777
|
+
| `ojm market setup [--url <站点>] [--user <u> --pass <p>] [--new-pass <p>]` | **外部真 CDN 场景才需要**:把 `framework.market_cdn_url` 改写为绝对地址;账号须 **superadmin**(该设置安全组仅 superadmin 可写,admin 角色会被 403);首跑若账号处于首次改密期会先自动改密再写入 |
|
|
1710
1778
|
| `ojm market publish [--fixture <name>@<version>] [--real <方案工程>] [--id <key>] [--key <pem>] [--rebuild-index] [--check]` | 把方案包落进 `cdn/` 并做包级 detached 签名;**不碰索引**(索引由 `build-index` 重建,避免多人发布互相覆盖) |
|
|
1711
1779
|
| `ojm market build-index [--id <key>] [--key <pem>] [--check]` | 扫描 `cdn/*.tgz`、提取包内 manifest,全量重建 `bundles.yaml` + `manifests.yaml` 并签名;结果只取决于目录内容,连续执行字节一致 |
|
|
1712
1780
|
| `ojm market verify [--file <tgz>] [--rebuild-index]` | 离线验包(不起站点、不装、不要凭据):sha256 完整性 / 包级 Ed25519 验签 / 索引验签 / manifest 结构分列输出;`--rebuild-index` 额外比对重建索引与盘上索引的漂移 |
|
|
1713
1781
|
| `ojm market trial [--fixture <name>@<version>] [--keep]` | 在一次性隔离站点里跑完整安装,跑完即毁;`--keep` 保留现场供排查 |
|
|
1782
|
+
| `ojm market walkthrough [--fixture <name>@<version>] [--only <op>] [--keep] [--strict]` | 隔离站点上 14 项全套自检(主链 9:index/install/reinstall/enable/verify/history/upgrade/rollback/uninstall;负向 5:tamper/nosig/badindex/stranger/evil——预期失败口径,防线没拦住才算 FAIL);AC 表 + 失败文案脱敏落盘 `logs/walkthrough-<ts>.md`;`--only` 单跑验原子性 |
|
|
1714
1783
|
|
|
1715
1784
|
- `--fixture <name>@<version>`:用内置合成包(无需先 build);`--real <方案工程>`:先 `ojm build --bundle` 再拿真实产物。
|
|
1716
1785
|
- `--id <key>` / `--key <pem>`:包级签名身份(与 `ojm sign` 同体系,默认取 `~/.ojm/keys`)。
|
|
@@ -1722,25 +1791,44 @@ export default async (path: string, html: string) => {
|
|
|
1722
1791
|
## §11 方案包:打包与分发
|
|
1723
1792
|
|
|
1724
1793
|
「站点级构建」见 §7;本节讲**方案包**(solution bundle)——单方案的可分发产物与到达站点的方式。
|
|
1794
|
+
一个脚手架可放多份用户方案(`ojm new` 逐个生成,各自一份 `src/<name>/solution.yaml`),
|
|
1795
|
+
`ojm build --bundle` 无参时全部打包、逐个校验独立出包;框架自带方案自动排除(名单见
|
|
1796
|
+
`src/.ojm-builtin.json`,`ojm info` 默认折叠自带模块)。
|
|
1797
|
+
**从开发到发布的完整过程视角(含签名与私钥保存纪律)见**
|
|
1798
|
+
[`docs/guides/solution-dev-build-publish.md`](../guides/solution-dev-build-publish.md)。
|
|
1725
1799
|
|
|
1726
1800
|
### 11.1 打包:`ojm build --bundle`
|
|
1727
1801
|
|
|
1728
1802
|
```bash
|
|
1729
1803
|
ojm build --bundle # --out <dir> 缺省工程根 dist/(方案包输出目录,与后端产物 api/dist 无关)
|
|
1730
|
-
#
|
|
1804
|
+
ojm build --bundle crm # 只打 src/crm/ 这一份;--bundle=crm 等价
|
|
1805
|
+
# [ojm] 方案包已生成 → dist/<name>-<version>.tgz(多份用户方案时逐份输出,共打包 N 份)
|
|
1731
1806
|
# [ojm] sha256:…(确定性打包:同源同字节)
|
|
1807
|
+
# 没有可打包的用户方案时:报错指向 ojm new(不静默产出空批次)
|
|
1732
1808
|
```
|
|
1733
1809
|
|
|
1734
1810
|
> `--bundle` 只**组装**,不重跑构建:入包内容取自 `api/dist/<name>-<version>/` 与
|
|
1735
1811
|
> `web/dist/modules/<name>/`——先 `ojm build` 再打包。
|
|
1736
1812
|
|
|
1737
|
-
-
|
|
1738
|
-
|
|
1813
|
+
- **契约入口(按优先级)**:`src/<模块>/solution.yaml` ≻ 工程根 `solution.yaml` ≻
|
|
1814
|
+
`src/<模块>/manifest.yaml` ≻ 工程根 `manifest.yaml`(方案级;`manifest.yaml` 为旧方案回退)。
|
|
1815
|
+
**单模块方案推荐把契约放在 `src/<模块>/solution.yaml`**——契约与它所打包的模块同位,最直观。
|
|
1816
|
+
文件名刻意避开 `manifest.yaml`:oj 按 `manifest.yaml` 枚举发现 api 后端模块(§1.2),工程根若放一份
|
|
1817
|
+
bundle 契约会污染那条搜索枚举;故用 `solution.yaml` 隔开两条枚举——**后端按 `manifest.yaml` 枚举 api、
|
|
1818
|
+
打包按 `solution.yaml` 取契约**,不再冲突。模块级契约同样**只认 `src/<模块>/` 根下、绝不进 `<模块>/api/`**,
|
|
1819
|
+
否则会与 oj 的 api 枚举撞车。tgz 内固定写回 `manifest.yaml`(安装侧只认这个名字)。
|
|
1820
|
+
- **两段式**:先对全部目标契约跑完校验(纯读零写盘),全绿才开始逐个写 tgz——任一校验失败
|
|
1821
|
+
dist/ 零增量,报错指名失败方案(发布者不会把半截批次扫进 CDN 索引)。校验项(fail-fast,
|
|
1822
|
+
坏清单出不了包):方案 solution 契约(字段齐全 / 类型白名单 / 保留名 / 键空间 → 可读违规清单)
|
|
1823
|
+
→ 文件面(`api.tables` ↔ `schema.yaml`、locales 双语齐备)→ 模块级契约 `name` 必须 === 目录名。
|
|
1824
|
+
- 框架自带方案不参与打包:`src/.ojm-builtin.json` 名单内的模块契约被跳过(逐条日志);
|
|
1825
|
+
该文件由 `ojm init` 从模板白名单派生,**可手改/删除**(删名 = 恢复参与打包),重跑 init 缺失才补写。
|
|
1826
|
+
它只是打包排除 / info 折叠提示,**不是安全边界**:文件永不入包,安装侧信任链(签名 + 信任库 + TOFU)与它无关。
|
|
1739
1827
|
- **只出包不签名**——签名见 §12(关注点分离:作者签名是显式动作,不是构建副产品)。
|
|
1740
1828
|
- 产物内 `manifest.yaml` 的 `archive` 段是 **Form A**(仅 `file`);`sha256`/`size` 由
|
|
1741
1829
|
file 源上传时点补齐(宿主安装器)或发布通道 Form B 自带。
|
|
1742
|
-
- **入包红线**:`*.pem` / `*.key` / `.env` / `id_ed25519` / `id_rsa`
|
|
1743
|
-
|
|
1830
|
+
- **入包红线**:`*.pem` / `*.key` / `*.p12` / `*.pfx` / `.env` 系 / `id_ed25519` / `id_rsa`
|
|
1831
|
+
等私钥形态文件禁入包(与 `modules.json` 禁入同款自检,0.2.23 起扩面)——私钥入包即信任根基烧毁。
|
|
1744
1832
|
|
|
1745
1833
|
### 11.2 两种分发源
|
|
1746
1834
|
|
|
@@ -1850,7 +1938,7 @@ ojm keygen --id <publisher>.<key-name>
|
|
|
1850
1938
|
ojm sign dist/<name>-<version>.tgz --id <publisher>.<key-name> --check
|
|
1851
1939
|
|
|
1852
1940
|
# ③ 安装/分发:.sig 自动探测随包上传
|
|
1853
|
-
ojm install dist/<name>-<version>.tgz --url <站点> --user
|
|
1941
|
+
ojm install dist/<name>-<version>.tgz --url <站点> --user superadmin --pass …
|
|
1854
1942
|
```
|
|
1855
1943
|
|
|
1856
1944
|
宿主验签六码(失败文案双语,`--locale` 切换):`sig_missing`(缺签)/ `sig_alg`(算法
|
|
@@ -68,15 +68,15 @@
|
|
|
68
68
|
|
|
69
69
|
模板自带一个**本地模拟 CDN**(工程根 `cdn/` + 工程根 `config.yaml` 里的 `/cdn` 挂载),配上 `ojm market` 四条命令,就能在本地复刻「发包 → 上架 → 索引 → 安装」全链路。不需要外部服务。
|
|
70
70
|
|
|
71
|
-
### 4.1
|
|
71
|
+
### 4.1 全流程(五步,step 1 已省)
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
|
-
# 0) 起站点(dev 或 serve
|
|
74
|
+
# 0) 起站点(dev 或 serve 均可)
|
|
75
75
|
ojm dev
|
|
76
76
|
|
|
77
|
-
# 1)
|
|
78
|
-
#
|
|
79
|
-
ojm market setup --user
|
|
77
|
+
# 1) 【免 setup】脚手架 seed 的 framework.market_cdn_url 缺省即 /cdn(本站点自供,
|
|
78
|
+
# dev 形态 ojm 已把 /cdn 反代给 oj 子进程)。只有指向外部真 CDN 时才需要:
|
|
79
|
+
# ojm market setup --user superadmin --pass 123456 --new-pass <你的口令>
|
|
80
80
|
|
|
81
81
|
# 2) 发包:合成 fixture(或 --real 走 ojm build --bundle)→ 确定性 tgz → 包级签名
|
|
82
82
|
# --rebuild-index 顺带重建索引
|
|
@@ -93,7 +93,7 @@ ojm market verify --rebuild-index
|
|
|
93
93
|
|
|
94
94
|
| 命令 | 做什么 | **不**做什么 |
|
|
95
95
|
|---|---|---|
|
|
96
|
-
| `market setup` | 写系统设置 `framework.market_cdn_url
|
|
96
|
+
| `market setup` | 写系统设置 `framework.market_cdn_url`(**外部真 CDN 场景才需要**;脚手架缺省 /cdn 免 setup) | 不改写工程根 `config.yaml`(`/cdn` 挂载由模板自带) |
|
|
97
97
|
| `market publish` | 落 `<name>-<version>.tgz` + 包级 `.tgz.sig` | **不碰索引**(索引是共享单文件,多人发布会互相覆盖) |
|
|
98
98
|
| `market build-index` | 扫 `cdn/*.tgz` 提取包内 manifest,全量重建两份索引并签名 | 不落包 |
|
|
99
99
|
| `market verify` | 完整性 / 包级签 / 索引签 / manifest 结构四类校验 + 漂移检出 | 不起站点、不装、不访问任何远端 |
|
|
@@ -111,9 +111,9 @@ ojm market verify --rebuild-index
|
|
|
111
111
|
|
|
112
112
|
## 5. 两个已经踩过的坑(别再踩)
|
|
113
113
|
|
|
114
|
-
### 5.1 dev 形态下,`/cdn`
|
|
114
|
+
### 5.1 dev 形态下,`/cdn` 由 oj 子进程直出(ojm 已代为反代)
|
|
115
115
|
|
|
116
|
-
`ojm dev` 的 Node 服务在 **5174
|
|
116
|
+
`ojm dev` 的 Node 服务在 **5174**,把 `/api` 与 `/cdn`(config 有 `/cdn` 挂载时)一并反代给 oj 子进程;`/cdn` 是 **oj 的挂载**,直出工程根 `cdn/`。`framework.market_cdn_url` 写成相对路径 `/cdn`(seed 缺省值)时,服务端按**请求源**补全同源绝对 URL 再拉取——dev 下请求走 5174,反代链路内透传,拿到的是真索引。2026-10-23 之前 dev 只反代 `/api`,相对基址会拿到静态层回落的 index.html(难懂的 YAML 解析错)——该坑已由 dev 反代 + 消费侧 `withSiteOrigin` 补全闭环。历史上下文:`market setup` 写绝对地址(`http://<host>:<config server.port>/cdn`)依旧兼容可用。
|
|
117
117
|
|
|
118
118
|
### 5.2 CDN 里摆了 `.tgz.sig` 侧车,不等于能装
|
|
119
119
|
|
|
@@ -165,7 +165,7 @@ node "$CLI_PKG/bin/ojm.mjs" market verify --rebuild-index
|
|
|
165
165
|
node "$CLI_PKG/bin/ojm.mjs" build # 生成 api/dist + web/dist(含 api/dist/versions.json)
|
|
166
166
|
# config:platform.root 改成 api/dist(安装目标恒为 release 部署,root 必须与 --api-path 同源)
|
|
167
167
|
(bin/oj serve -c config.yaml -b /api --api-path api/dist --console-log &)
|
|
168
|
-
node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user
|
|
168
|
+
node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user superadmin --pass 123456 --new-pass <新口令>
|
|
169
169
|
# 市场页点安装 → 首次停在「待信任」→ 核对指纹 → ojm trust confirm --task <id> → 重试 → done
|
|
170
170
|
```
|
|
171
171
|
|
|
@@ -174,7 +174,7 @@ node "$CLI_PKG/bin/ojm.mjs" market setup --url http://127.0.0.1:9778 --user admi
|
|
|
174
174
|
| # | 现象 | 根因 / 正确做法 |
|
|
175
175
|
|---|---|---|
|
|
176
176
|
| ① | `oj build` 报 `tables 声明 "x" 不在 schema.yaml` | oj 用 `schema.yaml` 做表结构真源,`manifest.tables` 只是引用面;补 `schema.yaml`(`tables: {<t>: {pk, columns}}`),与迁移语句对齐 |
|
|
177
|
-
| ② | `oj build` 成功但 `routes.js` 是空表、dist 里没有 handler | **只编译 `.ts` handler**;写成 `ping/api.js` 会被静默丢弃 → 改 `api.ts` |
|
|
177
|
+
| ② | `oj build` 成功但 `routes.js` 是空表、dist 里没有 handler | **只编译 `.ts` handler**;写成 `ping/api.js` 会被静默丢弃 → 改 `api.ts`。另一同形坑(构建也零报错):`api.ts` 写成裸具名导出(`export async function ping`)——oj 内省只读 **default export** 且链尾动词须在 METHODS 白名单(get/post/put/del/patch/head/options),裸导出的函数根本不在表里 → 改成 `const ping = { async get() {...} }; export default { ping }`(`ojm new --api` 骨架即此形状) |
|
|
178
178
|
| ③ | `build --bundle` 报 `缺少 src/<name>/web/locales/…`(历史布局是 `web/src/<name>/locales/…`) | manifest 一旦引用 `title_key`/`label_key` 就必须双语 locale(后端方案也要);且 key 必须**嵌套** `{"menu":{"main":"…"}}`,平铺 `"menu.main"` 查不到 |
|
|
179
179
|
| ④ | 安装 POST 返回 200,但 `market/task` 恒 404 | 宿主 config 缺 `fs:` 段 → 安装器落盘被 jail 拒、失败发生在任务文件写入前;0.2.13 起模板自带,旧工程手补 |
|
|
180
180
|
| ⑤ | 安装卡 `preflight`:`无法读取宿主 runtime 版本` | 安装器读 `api/dist/versions.json`;0.2.13 起 `ojm build` 双落点(旧版手抄 `web/dist/versions.json`) |
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
5
|
-
"description": "React Antd
|
|
4
|
+
"version": "0.2.25",
|
|
5
|
+
"description": "React Antd 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
@@ -49,9 +49,10 @@
|
|
|
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.25",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
|
+
"cac": "^6.7.14",
|
|
55
56
|
"esbuild": "^0.28.2",
|
|
56
57
|
"tailwindcss": "^4.3.0",
|
|
57
58
|
"tsx": "^4.23.12",
|
|
@@ -97,7 +98,6 @@
|
|
|
97
98
|
"zustand": "^5.0.13"
|
|
98
99
|
},
|
|
99
100
|
"publishConfig": {
|
|
100
|
-
"registry": "https://registry.npmjs.org/",
|
|
101
101
|
"access": "public"
|
|
102
102
|
},
|
|
103
103
|
"scripts": {
|
|
@@ -204,6 +204,7 @@ var init_site_base = __esmMin((() => {
|
|
|
204
204
|
* 子路径部署(/oj/wiki/...)下直接与 loginPath 比较恒为 false,401 时会重复 goLogin。
|
|
205
205
|
* React 组件内请用 `useLocation()`(react-router 已剥离 basename),无需本函数。
|
|
206
206
|
*
|
|
207
|
+
* @param pathname 原始路径(含部署 base,如 `location.pathname`)
|
|
207
208
|
* @param base 部署 base,恒以 `/` 结尾;缺省取 `resolveSiteBase()`(部署期注入优先,
|
|
208
209
|
* 见 utils/site-base.ts —— runtime 产物里的 `import.meta.env.BASE_URL` 是构建期
|
|
209
210
|
* 内联的 `/`,子路径部署下直接用它恒错)
|
|
@@ -303,10 +304,10 @@ function getAppInfo() {
|
|
|
303
304
|
return {
|
|
304
305
|
"pkg": {
|
|
305
306
|
"name": "@oj-module/runtime",
|
|
306
|
-
"version": "0.2.
|
|
307
|
+
"version": "0.2.25",
|
|
307
308
|
"license": "MIT"
|
|
308
309
|
},
|
|
309
|
-
"lastBuildTime": "2026-10-
|
|
310
|
+
"lastBuildTime": "2026-10-23 15:39:15"
|
|
310
311
|
};
|
|
311
312
|
}
|
|
312
313
|
var init_get_app_info = __esmMin((() => {}));
|
|
@@ -429,7 +430,7 @@ function PageError({ error, resetErrorBoundary }) {
|
|
|
429
430
|
};
|
|
430
431
|
useEffect(() => {
|
|
431
432
|
if (enableDynamicTitle) document.title = t("exception.pageErrorTitle");
|
|
432
|
-
}, [enableDynamicTitle]);
|
|
433
|
+
}, [enableDynamicTitle, t]);
|
|
433
434
|
return /* @__PURE__ */ jsx(Result, {
|
|
434
435
|
status: "error",
|
|
435
436
|
icon: /* @__PURE__ */ jsx("div", {
|
|
@@ -3495,7 +3496,7 @@ function useCurrentRoute() {
|
|
|
3495
3496
|
const matches = useMatches();
|
|
3496
3497
|
return useMemo(() => {
|
|
3497
3498
|
return matches[matches.length - 1];
|
|
3498
|
-
}, [matches
|
|
3499
|
+
}, [matches]);
|
|
3499
3500
|
}
|
|
3500
3501
|
var init_use_current_route = __esmMin((() => {}));
|
|
3501
3502
|
//#endregion
|
|
@@ -3595,7 +3596,7 @@ function useCssVar(name, root = defaultRoot, options = {}) {
|
|
|
3595
3596
|
}), [name, root]);
|
|
3596
3597
|
useEffect(() => {
|
|
3597
3598
|
if (options?.initialValue) controls.set(options.initialValue);
|
|
3598
|
-
}, [options?.initialValue]);
|
|
3599
|
+
}, [options?.initialValue, controls]);
|
|
3599
3600
|
useEffect(() => {
|
|
3600
3601
|
return () => controls.remove();
|
|
3601
3602
|
}, [controls]);
|
|
@@ -3656,7 +3657,7 @@ var init_dom = __esmMin((() => {}));
|
|
|
3656
3657
|
* @en Get the style of the layout content area
|
|
3657
3658
|
*/
|
|
3658
3659
|
function useLayoutContentStyle() {
|
|
3659
|
-
const
|
|
3660
|
+
const contentElementRef = useRef(null);
|
|
3660
3661
|
const [visibleDomRect, setVisibleDomRect] = useState(null);
|
|
3661
3662
|
const resizeObserverRef = useRef(null);
|
|
3662
3663
|
const contentHeightControls = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
|
|
@@ -3673,7 +3674,7 @@ function useLayoutContentStyle() {
|
|
|
3673
3674
|
};
|
|
3674
3675
|
}, [visibleDomRect]);
|
|
3675
3676
|
const debouncedCalcHeight = useDebounceFn((_entries) => {
|
|
3676
|
-
const rect = getElementVisibleRect(
|
|
3677
|
+
const rect = getElementVisibleRect(contentElementRef.current);
|
|
3677
3678
|
setVisibleDomRect(rect);
|
|
3678
3679
|
if (rect) {
|
|
3679
3680
|
contentHeightControls.set(`${rect.height}px`);
|
|
@@ -3681,10 +3682,10 @@ function useLayoutContentStyle() {
|
|
|
3681
3682
|
}
|
|
3682
3683
|
}, { wait: 16 });
|
|
3683
3684
|
useEffect(() => {
|
|
3684
|
-
if (
|
|
3685
|
+
if (contentElementRef.current && !resizeObserverRef.current) {
|
|
3685
3686
|
const resizeObserver = new ResizeObserver(debouncedCalcHeight.run);
|
|
3686
3687
|
resizeObserverRef.current = resizeObserver;
|
|
3687
|
-
resizeObserver.observe(
|
|
3688
|
+
resizeObserver.observe(contentElementRef.current);
|
|
3688
3689
|
}
|
|
3689
3690
|
return () => {
|
|
3690
3691
|
resizeObserverRef.current?.disconnect();
|
|
@@ -3692,7 +3693,7 @@ function useLayoutContentStyle() {
|
|
|
3692
3693
|
};
|
|
3693
3694
|
}, [debouncedCalcHeight]);
|
|
3694
3695
|
return {
|
|
3695
|
-
|
|
3696
|
+
contentElementRef,
|
|
3696
3697
|
overlayStyle,
|
|
3697
3698
|
visibleDomRect
|
|
3698
3699
|
};
|
|
@@ -4173,7 +4174,7 @@ function KeepAliveLayer() {
|
|
|
4173
4174
|
(aliveRef.current?.getCacheNodes?.())?.forEach((node) => {
|
|
4174
4175
|
if (!openTabs.has(node.cacheKey)) aliveRef.current?.destroy(node.cacheKey);
|
|
4175
4176
|
});
|
|
4176
|
-
}, [openTabs]);
|
|
4177
|
+
}, [openTabs, aliveRef]);
|
|
4177
4178
|
/**
|
|
4178
4179
|
* 关闭多 tab 功能时,清空所有非当前页面缓存
|
|
4179
4180
|
*/
|
|
@@ -4181,10 +4182,18 @@ function KeepAliveLayer() {
|
|
|
4181
4182
|
if (!tabbarEnable) (aliveRef.current?.getCacheNodes?.())?.forEach((node) => {
|
|
4182
4183
|
if (node.cacheKey !== cacheKey) aliveRef.current?.destroy(node.cacheKey);
|
|
4183
4184
|
});
|
|
4184
|
-
}, [
|
|
4185
|
+
}, [
|
|
4186
|
+
tabbarEnable,
|
|
4187
|
+
aliveRef,
|
|
4188
|
+
cacheKey
|
|
4189
|
+
]);
|
|
4185
4190
|
useEffect(() => {
|
|
4186
4191
|
if (tabbarEnable && isRefresh) aliveRef.current?.refresh();
|
|
4187
|
-
}, [
|
|
4192
|
+
}, [
|
|
4193
|
+
isRefresh,
|
|
4194
|
+
aliveRef,
|
|
4195
|
+
tabbarEnable
|
|
4196
|
+
]);
|
|
4188
4197
|
const keepAliveExclude = useMemo(() => {
|
|
4189
4198
|
/**
|
|
4190
4199
|
* 不开启多 tab 时不需要 KeepAlive,把所有路由放进 exclude 仅保留切换动画
|
|
@@ -4212,7 +4221,7 @@ var init_keep_alive_layer = __esmMin((() => {
|
|
|
4212
4221
|
//#region src/layout/layout-content/index.tsx
|
|
4213
4222
|
function LayoutContent() {
|
|
4214
4223
|
const { token: { colorBgLayout } } = theme.useToken();
|
|
4215
|
-
const {
|
|
4224
|
+
const { contentElementRef } = useLayoutContentStyle();
|
|
4216
4225
|
const enableFooter = usePreferencesStore((state) => state.enableFooter);
|
|
4217
4226
|
const fixedFooter = usePreferencesStore((state) => state.fixedFooter);
|
|
4218
4227
|
/**
|
|
@@ -4221,7 +4230,7 @@ function LayoutContent() {
|
|
|
4221
4230
|
*/
|
|
4222
4231
|
return /* @__PURE__ */ jsx("main", {
|
|
4223
4232
|
id: ELEMENT_ID_MAIN_CONTENT,
|
|
4224
|
-
ref:
|
|
4233
|
+
ref: contentElementRef,
|
|
4225
4234
|
className: "relative overflow-y-auto overflow-x-hidden grow",
|
|
4226
4235
|
style: { backgroundColor: colorBgLayout },
|
|
4227
4236
|
children: /* @__PURE__ */ jsx(Scrollbar, { children: /* @__PURE__ */ jsx(GlobalSpin, { children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -4447,7 +4456,11 @@ function GlobalSearch() {
|
|
|
4447
4456
|
useKeyPress("downarrow", handleDown);
|
|
4448
4457
|
useEffect(() => {
|
|
4449
4458
|
if (!keyword.length && Array.isArray(searchHistory)) setResultOptions(searchMenuList.filter((item) => searchHistory?.includes(item.key)));
|
|
4450
|
-
}, [
|
|
4459
|
+
}, [
|
|
4460
|
+
keyword,
|
|
4461
|
+
searchHistory,
|
|
4462
|
+
searchMenuList
|
|
4463
|
+
]);
|
|
4451
4464
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
4452
4465
|
onClick: () => setOpen((open) => !open),
|
|
4453
4466
|
className: "group flex justify-center items-center gap-2 md:bg-colorBgLayout px-3 py-1.5 rounded-full cursor-pointer text-colorTextSecondary hover:text-colorText md:mr-2.5",
|
|
@@ -5113,18 +5126,18 @@ function Sidebar() {
|
|
|
5113
5126
|
name: item.name,
|
|
5114
5127
|
checked: item.value,
|
|
5115
5128
|
onChange: handleChange,
|
|
5116
|
-
|
|
5117
|
-
|
|
5129
|
+
disabled: item.disabled,
|
|
5130
|
+
children: item.label
|
|
5118
5131
|
}, item.name);
|
|
5119
5132
|
}),
|
|
5120
5133
|
/* @__PURE__ */ jsx(SwitchItem, {
|
|
5121
5134
|
name: "sidebarTheme",
|
|
5122
5135
|
checked: sidebarTheme === "light",
|
|
5123
5136
|
onChange: (name, value) => setPreferences(name, value ? "light" : "dark"),
|
|
5124
|
-
children: t("preferences.sidebar.sidebarTheme"),
|
|
5125
5137
|
disabled: !sidebarEnable,
|
|
5126
5138
|
unCheckedChildren: /* @__PURE__ */ jsx(RiContrastFill, {}),
|
|
5127
|
-
checkedChildren: /* @__PURE__ */ jsx(RiMoonIcon, {})
|
|
5139
|
+
checkedChildren: /* @__PURE__ */ jsx(RiMoonIcon, {}),
|
|
5140
|
+
children: t("preferences.sidebar.sidebarTheme")
|
|
5128
5141
|
}),
|
|
5129
5142
|
/* @__PURE__ */ jsx(NumberInputSpinner, {
|
|
5130
5143
|
min: 40,
|
|
@@ -5702,7 +5715,7 @@ function UserMenu({ ...restProps }) {
|
|
|
5702
5715
|
}
|
|
5703
5716
|
if (key === "personal-center") navigate("/personal-center/my-profile");
|
|
5704
5717
|
};
|
|
5705
|
-
const altView = useMemo(() => isWindowsOs() ? "Alt" : "⌥", [
|
|
5718
|
+
const altView = useMemo(() => isWindowsOs() ? "Alt" : "⌥", []);
|
|
5706
5719
|
const items = [{
|
|
5707
5720
|
label: t("personal-center:menu.personalCenter"),
|
|
5708
5721
|
key: "personal-center",
|
|
@@ -5973,7 +5986,7 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
5973
5986
|
isMobile,
|
|
5974
5987
|
sidebarCollapsed
|
|
5975
5988
|
]);
|
|
5976
|
-
const handleOpenChange = (keys) => {
|
|
5989
|
+
const handleOpenChange = useCallback((keys) => {
|
|
5977
5990
|
/**
|
|
5978
5991
|
* 1. 手风琴模式,点击菜单项,自动关闭其他菜单
|
|
5979
5992
|
* 2. 非手风琴模式且菜单是收起的,鼠标悬浮菜单自动关闭其他菜单
|
|
@@ -5992,7 +6005,12 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
5992
6005
|
if (currentCloseKey) setOpenKeys(menuParentKeys[currentCloseKey]);
|
|
5993
6006
|
}
|
|
5994
6007
|
} else setOpenKeys(keys);
|
|
5995
|
-
}
|
|
6008
|
+
}, [
|
|
6009
|
+
accordion,
|
|
6010
|
+
sidebarCollapsed,
|
|
6011
|
+
openKeys,
|
|
6012
|
+
menuParentKeys
|
|
6013
|
+
]);
|
|
5996
6014
|
const menuOpenProps = useMemo(() => {
|
|
5997
6015
|
if (autoExpandCurrentMenu) return {
|
|
5998
6016
|
openKeys,
|
|
@@ -6019,7 +6037,8 @@ function LayoutMenu({ mode = "inline", autoExpandCurrentMenu, handleMenuSelect,
|
|
|
6019
6037
|
}, [
|
|
6020
6038
|
matches,
|
|
6021
6039
|
sidebarCollapsed,
|
|
6022
|
-
getSelectedKeys
|
|
6040
|
+
getSelectedKeys,
|
|
6041
|
+
accordion
|
|
6023
6042
|
]);
|
|
6024
6043
|
return /* @__PURE__ */ jsx(Menu, {
|
|
6025
6044
|
/**
|
|
@@ -6552,7 +6571,6 @@ function useDropdownMenu() {
|
|
|
6552
6571
|
}, [
|
|
6553
6572
|
t,
|
|
6554
6573
|
activeKey,
|
|
6555
|
-
homePath,
|
|
6556
6574
|
openTabs
|
|
6557
6575
|
]);
|
|
6558
6576
|
/**
|
|
@@ -6569,6 +6587,8 @@ function useDropdownMenu() {
|
|
|
6569
6587
|
[TabActionKeys.CLOSE_OTHERS]: closeOtherTabs,
|
|
6570
6588
|
[TabActionKeys.CLOSE_ALL]: closeAllTabs
|
|
6571
6589
|
}), [
|
|
6590
|
+
refresh,
|
|
6591
|
+
setIsRefresh,
|
|
6572
6592
|
removeTab,
|
|
6573
6593
|
closeRightTabs,
|
|
6574
6594
|
closeLeftTabs,
|
|
@@ -6814,7 +6834,7 @@ function LayoutTabbar() {
|
|
|
6814
6834
|
hash: ""
|
|
6815
6835
|
};
|
|
6816
6836
|
navigate(key + historyState.search + historyState.hash);
|
|
6817
|
-
}, [openTabs]);
|
|
6837
|
+
}, [openTabs, navigate]);
|
|
6818
6838
|
/**
|
|
6819
6839
|
* 处理标签页编辑(关闭)
|
|
6820
6840
|
* @param {React.MouseEvent | React.KeyboardEvent | string} key - 被编辑的标签页的key
|
|
@@ -6941,7 +6961,8 @@ function LayoutTabbar() {
|
|
|
6941
6961
|
location,
|
|
6942
6962
|
currentRoute,
|
|
6943
6963
|
setActiveKey,
|
|
6944
|
-
addTab
|
|
6964
|
+
addTab,
|
|
6965
|
+
activeKey
|
|
6945
6966
|
]);
|
|
6946
6967
|
return /* @__PURE__ */ jsx("div", {
|
|
6947
6968
|
className: classes.tabsContainer,
|
|
@@ -7023,7 +7044,7 @@ function ContainerLayout() {
|
|
|
7023
7044
|
if (screens.lg && !screens.xl) setPreferences("sidebarCollapsed", true);
|
|
7024
7045
|
else if (screens.xl) setPreferences("sidebarCollapsed", false);
|
|
7025
7046
|
else if (screens.xs || screens.sm && !screens.md) setPreferences("sidebarCollapsed", false);
|
|
7026
|
-
}, [screens]);
|
|
7047
|
+
}, [screens, setPreferences]);
|
|
7027
7048
|
const sidebarEnableState = useMemo(() => !isTopNav && sidebarEnable, [isTopNav, sidebarEnable]);
|
|
7028
7049
|
const computedSidebarWidth = useMemo(() => {
|
|
7029
7050
|
if (isMaximize || isMobile) return 0;
|
|
@@ -7049,7 +7070,7 @@ function ContainerLayout() {
|
|
|
7049
7070
|
let height = 48;
|
|
7050
7071
|
if (tabbarEnable) height += 35;
|
|
7051
7072
|
return height;
|
|
7052
|
-
}, [tabbarEnable
|
|
7073
|
+
}, [tabbarEnable]);
|
|
7053
7074
|
useEffect(() => {
|
|
7054
7075
|
setLayoutHeaderHeight(isMaximize ? 35 : headerWrapperHeight);
|
|
7055
7076
|
}, [headerWrapperHeight, isMaximize]);
|
|
@@ -7934,7 +7955,7 @@ async function generateRoutesFromBackend(backendRoutes) {
|
|
|
7934
7955
|
/**
|
|
7935
7956
|
* 转换路由配置
|
|
7936
7957
|
* @param route 原始路由配置
|
|
7937
|
-
* @param
|
|
7958
|
+
* @param parentComponentPath 父级组件路径(用于嵌套路由)
|
|
7938
7959
|
* @returns 转换后的路由配置
|
|
7939
7960
|
*/
|
|
7940
7961
|
const transformRoute = async (route, parentComponentPath) => {
|
|
@@ -8853,9 +8874,10 @@ async function handleErrorResponse(response) {
|
|
|
8853
8874
|
openMustChangeModal();
|
|
8854
8875
|
return response;
|
|
8855
8876
|
}
|
|
8877
|
+
if (response.status === 403) return response;
|
|
8856
8878
|
const json = data;
|
|
8857
8879
|
message$1.error(json.msg || response.statusText);
|
|
8858
|
-
} else message$1.error(response.statusText);
|
|
8880
|
+
} else if (response.status !== 403) message$1.error(response.statusText);
|
|
8859
8881
|
return response;
|
|
8860
8882
|
}
|
|
8861
8883
|
var init_error_response = __esmMin((() => {
|
|
@@ -8918,8 +8940,6 @@ var init_remember_route = __esmMin((() => {
|
|
|
8918
8940
|
* 必须带上部署 base(`resolveSiteBase()`):子路径部署下 `location.href = "/login"`
|
|
8919
8941
|
* 会跳到宿主机根,而不是 `/oj/login`。不能用 `import.meta.env.BASE_URL`——预构建
|
|
8920
8942
|
* 产物里它是构建期内联的 `/`。
|
|
8921
|
-
*
|
|
8922
|
-
* @returns 无返回值
|
|
8923
8943
|
*/
|
|
8924
8944
|
function goLogin() {
|
|
8925
8945
|
useAuthStore.getState().reset();
|
|
@@ -9022,6 +9042,8 @@ function onRefreshFailed(error) {
|
|
|
9022
9042
|
* 订阅者对象应包含 resolve 和 reject 方法。
|
|
9023
9043
|
*
|
|
9024
9044
|
* @param subscriber 订阅者对象,包含 resolve 和 reject 方法
|
|
9045
|
+
* @param subscriber.resolve 当 token 刷新成功时调用的函数,传入新的 token
|
|
9046
|
+
* @param subscriber.reject 当 token 刷新失败时调用的函数,传入错误信息
|
|
9025
9047
|
*/
|
|
9026
9048
|
function addRefreshSubscriber(subscriber) {
|
|
9027
9049
|
refreshSubscribers.push(subscriber);
|
|
@@ -9447,6 +9469,10 @@ function useAccess() {
|
|
|
9447
9469
|
* 权限验证组件
|
|
9448
9470
|
*
|
|
9449
9471
|
* @param AccessControlProps 权限验证组件的属性
|
|
9472
|
+
* @param AccessControlProps.type 权限类型,默认为 code
|
|
9473
|
+
* @param AccessControlProps.codes 权限值,可以是字符串或字符串数组
|
|
9474
|
+
* @param AccessControlProps.children 有权限时渲染的子组件
|
|
9475
|
+
* @param AccessControlProps.fallback 无权限时显示的内容
|
|
9450
9476
|
* @returns 若子组件存在,并且传入的权限值有效,则返回子组件;否则返回 null
|
|
9451
9477
|
*/
|
|
9452
9478
|
function AccessControl({ type = "code", codes, children, fallback }) {
|
|
@@ -9800,7 +9826,7 @@ function BasicTable(props) {
|
|
|
9800
9826
|
if (enableFooter && fixedFooter) return 40;
|
|
9801
9827
|
return 0;
|
|
9802
9828
|
}, [enableFooter, fixedFooter]);
|
|
9803
|
-
const getPaginationProps = () => {
|
|
9829
|
+
const getPaginationProps = useCallback(() => {
|
|
9804
9830
|
if (props.pagination === false) return false;
|
|
9805
9831
|
return {
|
|
9806
9832
|
placement: ["bottomStart"],
|
|
@@ -9810,7 +9836,7 @@ function BasicTable(props) {
|
|
|
9810
9836
|
showTotal: (total) => t("common.pagination", { total }),
|
|
9811
9837
|
...props.pagination
|
|
9812
9838
|
};
|
|
9813
|
-
};
|
|
9839
|
+
}, [props.pagination, t]);
|
|
9814
9840
|
/**
|
|
9815
9841
|
* @description 计算分页器的高度
|
|
9816
9842
|
* 如果分页器被禁用,则返回 0,否则根据分页器的大小返回相应的高度
|
package/shell-dist/index.html
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" href="/favicon.ico" />
|
|
7
7
|
<title>React Antd Admin · Shell</title>
|
|
8
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-rkh8kQ0W+uFw3wTdIkSSUA=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
|
|
9
9
|
|
|
10
|
-
<script type="importmap" nonce="
|
|
10
|
+
<script type="importmap" nonce="rkh8kQ0W+uFw3wTdIkSSUA==">{
|
|
11
11
|
"imports": {
|
|
12
12
|
"react": "/assets/react.js",
|
|
13
13
|
"react/jsx-runtime": "/assets/jsx-runtime.js",
|