@oj-module/cli 0.2.2 → 0.2.5

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 (231) hide show
  1. package/devkit/README.md +14 -3
  2. package/devkit/SKILL.md +16 -4
  3. package/devkit/framework-dev.md +8 -6
  4. package/devkit/manual.md +243 -23
  5. package/package.json +2 -2
  6. package/shell-dist/assets/runtime.js +143 -8
  7. package/shell-dist/index.html +2 -2
  8. package/shell-dist/versions.json +3 -3
  9. package/src/args.ts +67 -0
  10. package/src/build.ts +11 -3
  11. package/src/bundle.ts +197 -0
  12. package/src/contract/__tests__/handler-scan.test.ts +82 -0
  13. package/src/contract/check.ts +2 -1
  14. package/src/contract/handler-scan.ts +32 -0
  15. package/src/deterministic-tar.ts +120 -0
  16. package/src/dev.ts +4 -2
  17. package/src/enable.ts +28 -0
  18. package/src/index.ts +63 -2
  19. package/src/info.ts +3 -2
  20. package/src/init.ts +147 -14
  21. package/src/install.ts +268 -0
  22. package/src/keygen.ts +88 -0
  23. package/src/layout.ts +31 -0
  24. package/src/lifecycle/__tests__/authoring.test.ts +109 -0
  25. package/src/lifecycle/__tests__/bundle.test.ts +150 -0
  26. package/src/lifecycle/__tests__/deterministic-tar.test.ts +145 -0
  27. package/src/lifecycle/__tests__/fixtures/README.md +59 -0
  28. package/src/lifecycle/__tests__/fixtures/fixture-invalid/EXPECTED-VIOLATIONS.md +27 -0
  29. package/src/lifecycle/__tests__/fixtures/fixture-invalid/api/src/Demo/manifest.yaml +6 -0
  30. package/src/lifecycle/__tests__/fixtures/fixture-invalid/manifest.yaml +40 -0
  31. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/entry.ts +14 -0
  32. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/en-US.json +12 -0
  33. package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/zh-CN.json +12 -0
  34. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +9 -0
  35. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +6 -0
  36. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/ping/api.ts +9 -0
  37. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +9 -0
  38. package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +96 -0
  39. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/entry.ts +23 -0
  40. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/en-US.json +29 -0
  41. package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/zh-CN.json +29 -0
  42. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz +0 -0
  43. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.bad.sig +7 -0
  44. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.sig +7 -0
  45. package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.unknown.sig +7 -0
  46. package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.d.mts +21 -0
  47. package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.mjs +196 -0
  48. package/src/lifecycle/__tests__/helpers.ts +78 -0
  49. package/src/lifecycle/__tests__/install.test.ts +514 -0
  50. package/src/lifecycle/__tests__/keygen.test.ts +61 -0
  51. package/src/lifecycle/__tests__/manifest-contract.test.ts +228 -0
  52. package/src/lifecycle/__tests__/publish.test.ts +80 -0
  53. package/src/lifecycle/__tests__/sig-fixtures.test.ts +100 -0
  54. package/src/lifecycle/__tests__/sign.test.ts +131 -0
  55. package/src/lifecycle/__tests__/smoke.test.ts +61 -0
  56. package/src/lifecycle/__tests__/trust.test.ts +177 -0
  57. package/src/lifecycle/__tests__/uninstall.test.ts +87 -0
  58. package/src/lifecycle/keygen.ts +40 -0
  59. package/src/lifecycle/sign.ts +74 -0
  60. package/src/manifest-contract.ts +696 -0
  61. package/src/oj-cert.ts +19 -0
  62. package/src/oj-cmd.ts +3 -3
  63. package/src/preview.ts +2 -2
  64. package/src/publish.ts +150 -0
  65. package/src/sign.ts +128 -0
  66. package/src/site.ts +146 -0
  67. package/src/trust.ts +116 -0
  68. package/src/uninstall.ts +29 -0
  69. package/src/usage.ts +35 -1
  70. package/templates/README.md +1 -1
  71. package/templates/api/config.yaml +47 -0
  72. package/templates/api/seed.mysql.sql +1 -1
  73. package/templates/api/seed.pg.sql +1 -1
  74. package/templates/api/src/_platform/_lifecycle/atomic.ts +98 -0
  75. package/templates/api/src/_platform/_lifecycle/history.ts +31 -0
  76. package/templates/api/src/_platform/_lifecycle/installer.ts +1080 -0
  77. package/templates/api/src/_platform/_lifecycle/lock.ts +148 -0
  78. package/templates/api/src/_platform/_lifecycle/semver.ts +61 -0
  79. package/templates/api/src/_platform/_lifecycle/sha384.ts +187 -0
  80. package/templates/api/src/_platform/_lifecycle/state.ts +256 -0
  81. package/templates/api/src/_platform/_lifecycle/steps/deploy-web.ts +182 -0
  82. package/templates/api/src/_platform/_lifecycle/steps/download.ts +90 -0
  83. package/templates/api/src/_platform/_lifecycle/steps/extract.ts +239 -0
  84. package/templates/api/src/_platform/_lifecycle/steps/migrate.ts +363 -0
  85. package/templates/api/src/_platform/_lifecycle/steps/preflight.ts +590 -0
  86. package/templates/api/src/_platform/_lifecycle/steps/register.ts +211 -0
  87. package/templates/api/src/_platform/_lifecycle/steps/reload.ts +81 -0
  88. package/templates/api/src/_platform/_lifecycle/steps/smoke.ts +216 -0
  89. package/templates/api/src/_platform/_lifecycle/steps/verify.ts +207 -0
  90. package/templates/api/src/_platform/_lifecycle/tasks.ts +119 -0
  91. package/templates/api/src/_platform/_lifecycle/text-edit.ts +110 -0
  92. package/templates/api/src/_platform/_lifecycle/tgz-guard.ts +41 -0
  93. package/templates/api/src/_platform/_lifecycle/trust.ts +229 -0
  94. package/templates/api/src/_platform/_lifecycle/types.ts +94 -0
  95. package/templates/api/src/_platform/_lifecycle/uninstall.ts +283 -0
  96. package/templates/api/src/_platform/_lifecycle/verify-checks.ts +228 -0
  97. package/templates/api/src/_platform/_lifecycle/verify-report.ts +92 -0
  98. package/templates/api/src/_platform/_shared/service/access.ts +2 -2
  99. package/templates/api/src/_platform/_shared/service/settings.ts +10 -1
  100. package/templates/api/src/_platform/_shared/service/solution-perms.ts +14 -7
  101. package/templates/api/src/_platform/_shared/site-facade.ts +44 -0
  102. package/templates/api/src/_platform/manifest.yaml +1 -0
  103. package/templates/api/src/_platform/market/api.ts +883 -0
  104. package/templates/api/src/_platform/market/bootstrap.ts +39 -0
  105. package/templates/api/src/_platform/migrations/0010__sys_solution_route.mysql.sql +17 -0
  106. package/templates/api/src/_platform/migrations/0010__sys_solution_route.pg.sql +16 -0
  107. package/templates/api/src/_platform/migrations/0010__sys_solution_route.sql +18 -0
  108. package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.mysql.sql +2 -0
  109. package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.sql +4 -0
  110. package/templates/api/src/_platform/migrations/0012__market_history.mysql.sql +13 -0
  111. package/templates/api/src/_platform/migrations/0012__market_history.pg.sql +12 -0
  112. package/templates/api/src/_platform/migrations/0012__market_history.sql +14 -0
  113. package/templates/api/src/_platform/schema.yaml +21 -4
  114. package/templates/api/src/_platform/seed.sql +2 -2
  115. package/templates/api/src/_platform/solutions/api.ts +212 -0
  116. package/templates/api/src/demo/manifest.yaml +5 -0
  117. package/templates/api/src/demo/migrations/0001__create_todos.sql +9 -0
  118. package/templates/api/src/demo/schema.yaml +11 -0
  119. package/templates/api/src/demo/seed.sql +5 -0
  120. package/templates/api/src/demo/todos/api.ts +59 -0
  121. package/templates/api/src/home/line/api.ts +57 -0
  122. package/templates/api/src/home/manifest.yaml +6 -0
  123. package/templates/api/src/home/migrations/0001__create_home_tables.sql +13 -0
  124. package/templates/api/src/home/pie/api.ts +40 -0
  125. package/templates/api/src/home/schema.yaml +14 -0
  126. package/templates/api/src/home/seed.sql +70 -0
  127. package/templates/api/src/system/oplog/api.ts +44 -2
  128. package/templates/api/tests/fixture.ts +16 -0
  129. package/templates/api/tests/lifecycle/atomic-state.spec.ts +183 -0
  130. package/templates/api/tests/lifecycle/bootstrap.spec.ts +116 -0
  131. package/templates/api/tests/lifecycle/deploy-smoke.spec.ts +492 -0
  132. package/templates/api/tests/lifecycle/installer.spec.ts +1218 -0
  133. package/templates/api/tests/lifecycle/market-api.spec.ts +971 -0
  134. package/templates/api/tests/lifecycle/migrate.spec.ts +575 -0
  135. package/templates/api/tests/lifecycle/preflight.spec.ts +255 -0
  136. package/templates/api/tests/lifecycle/register.spec.ts +194 -0
  137. package/templates/api/tests/lifecycle/semver.spec.ts +43 -0
  138. package/templates/api/tests/lifecycle/sha384.spec.ts +48 -0
  139. package/templates/api/tests/lifecycle/sig-fixtures.spec.ts +65 -0
  140. package/templates/api/tests/lifecycle/solutions-api.spec.ts +98 -0
  141. package/templates/api/tests/lifecycle/steps-fs.spec.ts +623 -0
  142. package/templates/api/tests/lifecycle/text-edit.spec.ts +177 -0
  143. package/templates/api/tests/lifecycle/tgz-guard.spec.ts +103 -0
  144. package/templates/api/tests/lifecycle/trust.spec.ts +310 -0
  145. package/templates/api/tests/lifecycle/uninstall.spec.ts +386 -0
  146. package/templates/api/tests/lifecycle/verify-checks.spec.ts +385 -0
  147. package/templates/api/tests/lifecycle/verify-report.spec.ts +290 -0
  148. package/templates/api/tests/rbac-settings.spec.ts +8 -0
  149. package/templates/init.include.yaml +56 -0
  150. package/templates/openapi.json +527 -0
  151. package/templates/package.json +52 -0
  152. package/templates/web/src/_platform/__client/api.schemas.ts +8 -0
  153. package/templates/web/src/_platform/__client/api.ts +335 -0
  154. package/templates/web/src/about/entry.ts +42 -0
  155. package/templates/web/src/about/locales/en-US.json +16 -0
  156. package/templates/web/src/about/locales/zh-CN.json +16 -0
  157. package/templates/web/src/about/pages/constants.ts +20 -0
  158. package/templates/web/src/about/pages/index.tsx +135 -0
  159. package/templates/web/src/access/entry.ts +74 -0
  160. package/templates/web/src/access/locales/en-US.json +46 -0
  161. package/templates/web/src/access/locales/zh-CN.json +46 -0
  162. package/templates/web/src/access/pages/access-mode/index.tsx +16 -0
  163. package/templates/web/src/access/pages/admin-visible/index.tsx +16 -0
  164. package/templates/web/src/access/pages/button-control/index.tsx +181 -0
  165. package/templates/web/src/access/pages/common-visible/index.tsx +16 -0
  166. package/templates/web/src/access/pages/page-control/index.tsx +142 -0
  167. package/templates/web/src/demo/__client/api.schemas.ts +20 -0
  168. package/templates/web/src/demo/__client/api.ts +89 -0
  169. package/templates/web/src/exception/entry.ts +80 -0
  170. package/templates/web/src/exception/locales/en-US.json +13 -0
  171. package/templates/web/src/exception/locales/zh-CN.json +13 -0
  172. package/templates/web/src/exception/pages/403/index.tsx +30 -0
  173. package/templates/web/src/exception/pages/404/index.tsx +30 -0
  174. package/templates/web/src/exception/pages/500/index.tsx +30 -0
  175. package/templates/web/src/exception/pages/unknown-component/index.tsx +38 -0
  176. package/templates/web/src/home/__client/api.schemas.ts +22 -0
  177. package/templates/web/src/home/__client/api.ts +113 -0
  178. package/templates/web/src/login/locales/en-US.json +6 -0
  179. package/templates/web/src/login/locales/zh-CN.json +6 -0
  180. package/templates/web/src/login/pages/login.tsx +67 -18
  181. package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +93 -0
  182. package/templates/web/src/oj-market/__tests__/HistoryDrawer.test.tsx +28 -0
  183. package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +69 -0
  184. package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +43 -0
  185. package/templates/web/src/oj-market/__tests__/UninstallConfirm.test.tsx +30 -0
  186. package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +70 -0
  187. package/templates/web/src/oj-market/__tests__/poll.test.ts +24 -0
  188. package/templates/web/src/oj-market/__tests__/render.tsx +14 -0
  189. package/templates/web/src/oj-market/__tests__/runtime-stub.ts +14 -0
  190. package/templates/web/src/oj-market/__tests__/semver.test.ts +26 -0
  191. package/templates/web/src/oj-market/api/MarketClientProvider.tsx +12 -0
  192. package/templates/web/src/oj-market/api/client.ts +135 -0
  193. package/templates/web/src/oj-market/api/context.ts +11 -0
  194. package/templates/web/src/oj-market/api/defaultClient.ts +7 -0
  195. package/templates/web/src/oj-market/components/CardGrid.tsx +76 -0
  196. package/templates/web/src/oj-market/components/CardItem.tsx +102 -0
  197. package/templates/web/src/oj-market/components/DetailDrawer.tsx +53 -0
  198. package/templates/web/src/oj-market/components/HistoryDrawer.tsx +75 -0
  199. package/templates/web/src/oj-market/components/InstallDialog.tsx +112 -0
  200. package/templates/web/src/oj-market/components/RollbackDialog.tsx +79 -0
  201. package/templates/web/src/oj-market/components/UninstallConfirm.tsx +74 -0
  202. package/templates/web/src/oj-market/entry.ts +27 -0
  203. package/templates/web/src/oj-market/hooks/useMarket.ts +91 -0
  204. package/templates/web/src/oj-market/locales/en-US.json +100 -0
  205. package/templates/web/src/oj-market/locales/zh-CN.json +100 -0
  206. package/templates/web/src/oj-market/pages/MarketApp.tsx +136 -0
  207. package/templates/web/src/oj-market/types.ts +84 -0
  208. package/templates/web/src/oj-market/utils/poll.ts +36 -0
  209. package/templates/web/src/oj-market/utils/semver.ts +14 -0
  210. package/templates/web/src/outside/entry.ts +80 -0
  211. package/templates/web/src/outside/locales/en-US.json +10 -0
  212. package/templates/web/src/outside/locales/zh-CN.json +10 -0
  213. package/templates/web/src/personal-center/entry.ts +1 -10
  214. package/templates/web/src/personal-center/locales/en-US.json +1 -5
  215. package/templates/web/src/personal-center/locales/zh-CN.json +1 -5
  216. package/templates/web/src/route-nest/entry.ts +73 -0
  217. package/templates/web/src/route-nest/locales/en-US.json +9 -0
  218. package/templates/web/src/route-nest/locales/zh-CN.json +9 -0
  219. package/templates/web/src/route-nest/pages/menu1/menu1-1/index.tsx +5 -0
  220. package/templates/web/src/route-nest/pages/menu1/menu1-2/index.tsx +5 -0
  221. package/templates/web/src/route-nest/pages/menu2/index.tsx +5 -0
  222. package/templates/web/src/system/__client/api.schemas.ts +12 -0
  223. package/templates/web/src/system/__client/api.ts +25 -1
  224. package/templates/web/src/system/pages/oplog/index.tsx +1 -1
  225. package/templates/web/src/system/pages/roles/index.tsx +1 -0
  226. package/templates/web/src/system/pages/settings/index.tsx +1 -1
  227. package/templates/web/src/system/pages/users/index.tsx +1 -1
  228. package/templates/web.config.ts +32 -40
  229. package/vendor/host-versions.json +3 -3
  230. package/templates/pnpm-workspace.yaml +0 -14
  231. package/templates/web/src/personal-center/pages/settings/index.tsx +0 -5
@@ -0,0 +1,136 @@
1
+ import type { IndexRow, TaskRecord } from "../types";
2
+ import { Typography } from "antd";
3
+ import React, { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useMarketClient } from "../api/context";
6
+ import { CardGrid } from "../components/CardGrid";
7
+ import { DetailDrawer } from "../components/DetailDrawer";
8
+ import { HistoryDrawer } from "../components/HistoryDrawer";
9
+ import { InstallDialog } from "../components/InstallDialog";
10
+ import { RollbackDialog } from "../components/RollbackDialog";
11
+ import { UninstallConfirm } from "../components/UninstallConfirm";
12
+ import { useMarketIndex } from "../hooks/useMarket";
13
+
14
+ /** 市场应用主页面:组装卡片网格 + 详情/安装/卸载/回滚/历史交互面(单一容器,DRY 编排) */
15
+ export default function MarketApp() {
16
+ const { t } = useTranslation();
17
+ const client = useMarketClient();
18
+ const index = useMarketIndex();
19
+
20
+ const [detailRow, setDetailRow] = useState<IndexRow | null>(null);
21
+ const [installRow, setInstallRow] = useState<IndexRow | null>(null);
22
+ const [installVersion, setInstallVersion] = useState<string | null>(null);
23
+ const [installMode, setInstallMode] = useState<"install" | "upgrade" | "rollback">("install");
24
+ const [uninstallRow, setUninstallRow] = useState<IndexRow | null>(null);
25
+ const [rollbackRow, setRollbackRow] = useState<IndexRow | null>(null);
26
+ const [historyRow, setHistoryRow] = useState<IndexRow | null>(null);
27
+ const [busyName, setBusyName] = useState<string | null>(null);
28
+
29
+ const rows = index.status === "ok" ? index.data : [];
30
+
31
+ // 安装/升级:目标版本缺省(取 latest);已装视为升级
32
+ const handleInstall = (row: IndexRow) => {
33
+ setDetailRow(null);
34
+ setInstallRow(row);
35
+ setInstallVersion(null);
36
+ setInstallMode(row.installed != null ? "upgrade" : "install");
37
+ };
38
+
39
+ // 回滚:先开版本选择抽屉,选中旧版后转安装(降级安装,显式 version)
40
+ const handleRollback = (row: IndexRow) => {
41
+ setDetailRow(null);
42
+ setRollbackRow(row);
43
+ };
44
+
45
+ const handleRollbackChosen = (row: IndexRow, version: string) => {
46
+ setRollbackRow(null);
47
+ setInstallRow(row);
48
+ setInstallVersion(version);
49
+ setInstallMode("rollback");
50
+ };
51
+
52
+ const handleEnableToggle = async (row: IndexRow) => {
53
+ setBusyName(row.name);
54
+ try {
55
+ await client.enable({ name: row.name, enabled: row.enabled !== true });
56
+ index.retry();
57
+ }
58
+ finally {
59
+ setBusyName(null);
60
+ }
61
+ };
62
+
63
+ const handleUninstallConfirmed = async (row: IndexRow) => {
64
+ setBusyName(row.name);
65
+ try {
66
+ await client.uninstall(row.name);
67
+ setUninstallRow(null);
68
+ index.retry();
69
+ }
70
+ finally {
71
+ setBusyName(null);
72
+ }
73
+ };
74
+
75
+ const handleInstallDone = (_rec: TaskRecord) => {
76
+ index.retry();
77
+ };
78
+
79
+ return (
80
+ <div style={{ padding: 24 }} data-testid="market-app">
81
+ <Typography.Title level={4}>{t("oj-market:card:title")}</Typography.Title>
82
+ <Typography.Paragraph type="secondary">{t("oj-market:card:subtitle")}</Typography.Paragraph>
83
+
84
+ <CardGrid
85
+ loading={index.status === "loading"}
86
+ error={index.status === "error" ? index.error : null}
87
+ rows={rows}
88
+ busyName={busyName}
89
+ onInstall={handleInstall}
90
+ onEnableToggle={handleEnableToggle}
91
+ onDetails={r => setDetailRow(r)}
92
+ onHistory={r => setHistoryRow(r)}
93
+ onUninstall={r => setUninstallRow(r)}
94
+ onRollback={handleRollback}
95
+ onRetry={index.retry}
96
+ />
97
+
98
+ <DetailDrawer
99
+ row={detailRow}
100
+ open={detailRow != null}
101
+ onClose={() => setDetailRow(null)}
102
+ onInstall={handleInstall}
103
+ onHistory={(r) => {
104
+ setDetailRow(null);
105
+ setHistoryRow(r);
106
+ }}
107
+ onRollback={handleRollback}
108
+ />
109
+
110
+ <InstallDialog
111
+ row={installRow}
112
+ open={installRow != null}
113
+ onClose={() => setInstallRow(null)}
114
+ onDone={handleInstallDone}
115
+ version={installVersion}
116
+ mode={installMode}
117
+ />
118
+
119
+ <UninstallConfirm
120
+ row={uninstallRow}
121
+ open={uninstallRow != null}
122
+ onClose={() => setUninstallRow(null)}
123
+ onConfirmed={handleUninstallConfirmed}
124
+ />
125
+
126
+ <RollbackDialog
127
+ row={rollbackRow}
128
+ open={rollbackRow != null}
129
+ onClose={() => setRollbackRow(null)}
130
+ onRollback={handleRollbackChosen}
131
+ />
132
+
133
+ <HistoryDrawer row={historyRow} open={historyRow != null} onClose={() => setHistoryRow(null)} />
134
+ </div>
135
+ );
136
+ }
@@ -0,0 +1,84 @@
1
+ // 市场领域类型(形状对齐宿主 _platform 编排面真源 + 本仓 market_history 表)。
2
+ // 真源:templates/api/src/_platform/_lifecycle/types.ts:47 (TaskRecord) 与 market/api.ts:IndexRow。
3
+ // 任何形状漂移以 templates 源码为准——实现前已核对(2026-10-17)。
4
+
5
+ /** 安装阶段名(契约附录 A 十阶段逐字 + lock_commit/audit/done 终态) */
6
+ export type InstallStage = "preflight" | "downloading" | "verifying" | "extracting" | "migrating" | "lock_commit" | "registering" | "deploying_web" | "reloading" | "smoke" | "audit" | "done";
7
+
8
+ /** 任务状态(§10.2 三态 + partial:锁后失败部分生效) */
9
+ export type TaskStatus = "running" | "done" | "failed" | "partial";
10
+
11
+ /** 阶段明细日志(UI 详情日志区数据源) */
12
+ export interface TaskStageLog {
13
+ stage: InstallStage
14
+ ok: boolean
15
+ message: string
16
+ }
17
+
18
+ /** 失败/partial 终态:reason + retryable + 指引文案 key(契约附录 A) */
19
+ export interface TaskError {
20
+ reason: string
21
+ retryable: boolean
22
+ hintKey?: string
23
+ }
24
+
25
+ /** 安装任务记录(§10.2 + stages 明细日志;落 .install-tasks/<id>.json,800ms 轮询消费面) */
26
+ export interface TaskRecord {
27
+ id: string
28
+ name: string
29
+ version: string
30
+ status: TaskStatus
31
+ stage: InstallStage | null
32
+ /** 0-100 整体进度 */
33
+ progress: number
34
+ message: string
35
+ stages: TaskStageLog[]
36
+ error?: TaskError
37
+ startedAt: number
38
+ finishedAt?: number
39
+ appliedSeq?: number
40
+ }
41
+
42
+ /** 卡片数据源行(GET /_platform/market/index):CDN bundles.yaml ⊕ 本地 sidecar state */
43
+ export interface IndexRow {
44
+ name: string
45
+ latest: string | null
46
+ installed: string | null
47
+ enabled: boolean | null
48
+ upgradable: boolean
49
+ }
50
+
51
+ /** 安装输入(POST /_platform/market/install) */
52
+ export interface InstallInput {
53
+ name: string
54
+ version?: string | null
55
+ source?: "cdn" | "file"
56
+ }
57
+
58
+ /** 启用/停用输入(POST /_platform/market/enable) */
59
+ export interface EnableInput {
60
+ name: string
61
+ enabled: boolean
62
+ }
63
+
64
+ /** 市场操作历史动作 */
65
+ export type HistoryAction = "install" | "uninstall" | "enable" | "disable" | "upgrade" | "rollback";
66
+
67
+ /** 市场操作历史记录(本仓 market_history 表,只读消费) */
68
+ export interface MarketHistory {
69
+ id: number
70
+ solution: string
71
+ version: string
72
+ action: HistoryAction
73
+ status: string
74
+ operator: string | null
75
+ message: string | null
76
+ created_at: number
77
+ }
78
+
79
+ /** 卸载影响面预检(P3 U1 语义;服务端返回供前端二次确认) */
80
+ export interface UninstallImpact {
81
+ name: string
82
+ affectedRoles: number
83
+ affectedUsers: number
84
+ }
@@ -0,0 +1,36 @@
1
+ // 通用轮询工具(§10.2:市场任务 800ms 轮询、退避上限 5s、超时 30min 由调用方配置)。
2
+ // 纯函数、无副作用、可独立单测。
3
+
4
+ export interface PollOptions {
5
+ /** 轮询间隔(默认 800ms) */
6
+ intervalMs?: number
7
+ /** 整体超时(默认 30min) */
8
+ timeoutMs?: number
9
+ /** 中断信号 */
10
+ signal?: AbortSignal
11
+ }
12
+
13
+ /**
14
+ * 反复执行 fn,直到 isDone(value) 为真或超时。
15
+ * 超时返回最近一次取值(由调用方标记超时态),不抛错——避免轮询中断业务。
16
+ */
17
+ export async function poll<T>(
18
+ fn: () => Promise<T>,
19
+ isDone: (value: T) => boolean,
20
+ opts: PollOptions = {},
21
+ ): Promise<T> {
22
+ const interval = opts.intervalMs ?? 800;
23
+ const timeout = opts.timeoutMs ?? 30 * 60 * 1000;
24
+ const start = Date.now();
25
+ // 轮询循环:每轮先取最新值,终态即返回,超时则返回当前值
26
+ for (;;) {
27
+ if (opts.signal?.aborted)
28
+ throw new DOMException("aborted", "AbortError");
29
+ const value = await fn();
30
+ if (isDone(value))
31
+ return value;
32
+ if (Date.now() - start > timeout)
33
+ return value;
34
+ await new Promise<void>(resolve => setTimeout(resolve, interval));
35
+ }
36
+ }
@@ -0,0 +1,14 @@
1
+ // 版本比较(镜像 oj-module _lifecycle/semver 的最小口径,前端本地副本——宿主 semver 在
2
+ // 服务端隔离层,前端不可 import;点分/连字符预发布字段逐段数值比)。返回 <0 / 0 / >0。
3
+ // 用途:双版本卡片 upgradable 对比展示 + 回滚候选过滤(候选 = 版本 < 已装版本)。
4
+ export function compareVersions(a: string, b: string): number {
5
+ const pa = String(a).split(/[.\-]/).map(x => Number.parseInt(x, 10) || 0);
6
+ const pb = String(b).split(/[.\-]/).map(x => Number.parseInt(x, 10) || 0);
7
+ const n = Math.max(pa.length, pb.length);
8
+ for (let i = 0; i < n; i++) {
9
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
10
+ if (d !== 0)
11
+ return d < 0 ? -1 : 1;
12
+ }
13
+ return 0;
14
+ }
@@ -0,0 +1,80 @@
1
+ import type { AppRouteRecordRaw, ModuleDefinition } from "@oj-module/runtime";
2
+ import { AntDesignOutlined, ContainerOutlined } from "@ant-design/icons";
3
+
4
+ import { EmbeddedIcon, ExternalIcon, Iframe, OutsidePageIcon, RiReactjsLine } from "@oj-module/runtime";
5
+ import { createElement } from "react";
6
+ import { Outlet } from "react-router";
7
+
8
+ const routes: AppRouteRecordRaw[] = [
9
+ {
10
+ path: "/outside",
11
+ handle: {
12
+ layout: "container",
13
+ icon: createElement(OutsidePageIcon),
14
+ title: "outside:menu.outside",
15
+ order: 40,
16
+ },
17
+ children: [
18
+ {
19
+ path: "/outside/embedded",
20
+ Component: Outlet,
21
+ handle: {
22
+ icon: createElement(EmbeddedIcon),
23
+ title: "outside:menu.embedded",
24
+ },
25
+ children: [
26
+ {
27
+ path: "/outside/embedded/ant-design",
28
+ Component: Iframe,
29
+ handle: {
30
+ icon: createElement(AntDesignOutlined),
31
+ title: "outside:menu.antd",
32
+ iframeLink: "https://ant.design/",
33
+ },
34
+ },
35
+ {
36
+ path: "/outside/embedded/project-docs",
37
+ Component: Iframe,
38
+ handle: {
39
+ icon: createElement(ContainerOutlined),
40
+ title: "outside:menu.projectDocs",
41
+ iframeLink: "https://condorheroblog.github.io/react-antd-admin/docs/",
42
+ },
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ path: "/outside/external-link",
48
+ Component: Outlet,
49
+ handle: {
50
+ icon: createElement(ExternalIcon),
51
+ title: "outside:menu.externalLink",
52
+ },
53
+ children: [
54
+ {
55
+ path: "/outside/external-link/react-docs",
56
+ Component: Iframe,
57
+ handle: {
58
+ icon: createElement(RiReactjsLine),
59
+ title: "outside:menu.reactDocs",
60
+ externalLink: "https://react.dev/",
61
+ },
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ ];
68
+
69
+ const mod: ModuleDefinition = {
70
+ name: "outside",
71
+ description: "外部链接模块",
72
+ version: "1.0.0",
73
+ routes,
74
+ i18n: {
75
+ "zh-CN": () => import("./locales/zh-CN.json"),
76
+ "en-US": () => import("./locales/en-US.json"),
77
+ },
78
+ };
79
+
80
+ export default mod;
@@ -0,0 +1,10 @@
1
+ {
2
+ "menu": {
3
+ "outside": "External Page",
4
+ "embedded": "Embedded",
5
+ "externalLink": "External Link",
6
+ "antd": "Ant Design",
7
+ "projectDocs": "Project Docs",
8
+ "reactDocs": "React Docs"
9
+ }
10
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "menu": {
3
+ "outside": "外部页面",
4
+ "embedded": "内嵌",
5
+ "externalLink": "外链",
6
+ "antd": "Ant Design",
7
+ "projectDocs": "项目文档",
8
+ "reactDocs": "React 文档"
9
+ }
10
+ }
@@ -1,10 +1,9 @@
1
1
  import type { AppRouteRecordRaw, ModuleDefinition, UploadApiProvider } from "@oj-module/runtime";
2
- import { ProfileCardIcon, RiAccountCircleLine, RiUserSettingsLine, useAuthStore } from "@oj-module/runtime";
2
+ import { ProfileCardIcon, RiAccountCircleLine, useAuthStore } from "@oj-module/runtime";
3
3
 
4
4
  import { createElement, lazy } from "react";
5
5
 
6
6
  const MyProfile = lazy(() => import("./pages/my-profile"));
7
- const Settings = lazy(() => import("./pages/settings"));
8
7
 
9
8
  const routes: AppRouteRecordRaw[] = [
10
9
  {
@@ -24,14 +23,6 @@ const routes: AppRouteRecordRaw[] = [
24
23
  icon: createElement(ProfileCardIcon),
25
24
  },
26
25
  },
27
- {
28
- path: "/personal-center/settings",
29
- Component: Settings,
30
- handle: {
31
- title: "personal-center:menu.settings",
32
- icon: createElement(RiUserSettingsLine),
33
- },
34
- },
35
26
  ],
36
27
  },
37
28
  ];
@@ -1,10 +1,6 @@
1
1
  {
2
- "accountSecurity": "Account Security",
3
- "generalSettings": "General Settings",
4
- "notificationSettings": "Notification Settings",
5
2
  "menu": {
6
3
  "personalCenter": "Personal Center",
7
- "profile": "My Profile",
8
- "settings": "Settings"
4
+ "profile": "My Profile"
9
5
  }
10
6
  }
@@ -1,10 +1,6 @@
1
1
  {
2
- "accountSecurity": "账户安全",
3
- "generalSettings": "通用设置",
4
- "notificationSettings": "通知设置",
5
2
  "menu": {
6
3
  "personalCenter": "个人中心",
7
- "profile": "我的资料",
8
- "settings": "设置"
4
+ "profile": "我的资料"
9
5
  }
10
6
  }
@@ -0,0 +1,73 @@
1
+ import type { AppRouteRecordRaw, ModuleDefinition } from "@oj-module/runtime";
2
+
3
+ import {
4
+ NodeExpandOutlined,
5
+ SisternodeOutlined,
6
+ SubnodeOutlined,
7
+ } from "@ant-design/icons";
8
+ import { createElement, lazy } from "react";
9
+
10
+ const Menu1And1 = lazy(() => import("./pages/menu1/menu1-1"));
11
+ const Menu1And2 = lazy(() => import("./pages/menu1/menu1-2"));
12
+ const Menu2 = lazy(() => import("./pages/menu2"));
13
+
14
+ const routes: AppRouteRecordRaw[] = [
15
+ {
16
+ path: "/route-nest",
17
+ handle: {
18
+ order: 20,
19
+ title: "route-nest:menu.nestMenus",
20
+ icon: createElement(NodeExpandOutlined),
21
+ layout: "container",
22
+ },
23
+ children: [
24
+ {
25
+ path: "/route-nest/menu1",
26
+ handle: {
27
+ title: "route-nest:menu.menu1",
28
+ icon: createElement(SisternodeOutlined),
29
+ layout: "parent",
30
+ },
31
+ children: [
32
+ {
33
+ path: "/route-nest/menu1/menu1-1",
34
+ Component: Menu1And1,
35
+ handle: {
36
+ title: "route-nest:menu.menu1-1",
37
+ icon: createElement(SubnodeOutlined),
38
+ },
39
+ },
40
+ {
41
+ path: "/route-nest/menu1/menu1-2",
42
+ Component: Menu1And2,
43
+ handle: {
44
+ title: "route-nest:menu.menu1-2",
45
+ icon: createElement(SubnodeOutlined),
46
+ },
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ path: "/route-nest/menu2",
52
+ Component: Menu2,
53
+ handle: {
54
+ title: "route-nest:menu.menu2",
55
+ icon: createElement(SubnodeOutlined),
56
+ },
57
+ },
58
+ ],
59
+ },
60
+ ];
61
+
62
+ const mod: ModuleDefinition = {
63
+ name: "route-nest",
64
+ description: "嵌套路由模块",
65
+ version: "1.0.0",
66
+ routes,
67
+ i18n: {
68
+ "zh-CN": () => import("./locales/zh-CN.json"),
69
+ "en-US": () => import("./locales/en-US.json"),
70
+ },
71
+ };
72
+
73
+ export default mod;
@@ -0,0 +1,9 @@
1
+ {
2
+ "menu": {
3
+ "nestMenus": "Nest Menus",
4
+ "menu1": "Menu1",
5
+ "menu1-1": "Menu1-1",
6
+ "menu1-2": "Menu1-2",
7
+ "menu2": "Menu2"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "menu": {
3
+ "nestMenus": "嵌套菜单",
4
+ "menu1": "菜单 1",
5
+ "menu1-1": "菜单 1-1",
6
+ "menu1-2": "菜单 1-2",
7
+ "menu2": "菜单 2"
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ import { BasicContent } from "@oj-module/runtime";
2
+
3
+ export default function Menu1And1() {
4
+ return <BasicContent>menu1-1</BasicContent>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { BasicContent } from "@oj-module/runtime";
2
+
3
+ export default function Menu1And2() {
4
+ return <BasicContent>menu1-2</BasicContent>;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { BasicContent } from "@oj-module/runtime";
2
+
3
+ export default function Menu2() {
4
+ return <BasicContent>menu2</BasicContent>;
5
+ }
@@ -31,6 +31,18 @@ export const schemas = {
31
31
  })),
32
32
  "total": z.number(),
33
33
  "truncated": z.boolean()
34
+ }),
35
+ },
36
+ "systemOplogPost": {
37
+ body: z.object({
38
+ "action": z.string().min(1),
39
+ "module": z.string().min(1),
40
+ "version": z.string().optional(),
41
+ "target": z.string().optional(),
42
+ "level": z.number().optional()
43
+ }),
44
+ data: z.object({
45
+ "ok": z.boolean()
34
46
  }),
35
47
  },
36
48
  "systemPermissionsGet": {
@@ -33,7 +33,7 @@ export function createSystemClient(ctx: ModuleContext) {
33
33
  }
34
34
  ctx.register.apiPrefix(API_PREFIX);
35
35
  bindRequest(ctx.utils.request);
36
- return { systemLoginMethodsGet, systemOplogGet, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
36
+ return { systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
37
37
  }
38
38
 
39
39
  /**
@@ -111,6 +111,30 @@ export async function systemOplogGet(query: SystemOplogGetQuery): Promise<System
111
111
  }
112
112
  }
113
113
 
114
+ export type SystemOplogPostBody = z.input<(typeof schemas)["systemOplogPost"]["body"]>;
115
+ export type SystemOplogPostData = z.infer<(typeof schemas)["systemOplogPost"]["data"]>;
116
+
117
+ export async function systemOplogPost(body: SystemOplogPostBody): Promise<SystemOplogPostData> {
118
+ const client = ensureReq();
119
+ try {
120
+ const env = await client.post(`system/oplog`, { json: body }).json<OjEnvelope<SystemOplogPostData>>();
121
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
122
+ if (typeof env.code === "number" && env.code !== 0)
123
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
124
+ const data = env.data as SystemOplogPostData;
125
+ if (import.meta.env.DEV) {
126
+ const { schemas } = await import("./api.schemas");
127
+ const r = schemas["systemOplogPost"].data.safeParse(data);
128
+ if (!r.success)
129
+ throw new ContractApiError(-1, `[契约违例] systemOplogPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
130
+ }
131
+ return data;
132
+ }
133
+ catch (e) {
134
+ throw await toApiError(e);
135
+ }
136
+ }
137
+
114
138
  export type SystemPermissionsGetData = z.infer<(typeof schemas)["systemPermissionsGet"]["data"]>;
115
139
 
116
140
  export async function systemPermissionsGet(): Promise<SystemPermissionsGetData> {
@@ -47,7 +47,7 @@ export default function OplogPage() {
47
47
  ];
48
48
 
49
49
  return (
50
- <Space direction="vertical" style={{ width: "100%" }}>
50
+ <Space direction="vertical" style={{ width: "100%", paddingTop: 16 }}>
51
51
  <Form
52
52
  layout="inline"
53
53
  onFinish={values => setFilters({
@@ -67,6 +67,7 @@ export default function RolesPage() {
67
67
 
68
68
  return (
69
69
  <Tabs
70
+ style={{ paddingTop: 16 }}
70
71
  items={[
71
72
  {
72
73
  key: "roles",
@@ -91,7 +91,7 @@ export default function SettingsPage() {
91
91
  .filter(g => g.items.length > 0);
92
92
 
93
93
  return (
94
- <Space direction="vertical" style={{ width: "100%" }}>
94
+ <Space direction="vertical" style={{ width: "100%", paddingTop: 16 }}>
95
95
  {groups.map(g => (
96
96
  <Card key={g.group} title={g.group}>
97
97
  <Space direction="vertical" style={{ width: "100%" }}>
@@ -115,7 +115,7 @@ export default function UsersPage() {
115
115
  ];
116
116
 
117
117
  return (
118
- <Space direction="vertical" style={{ width: "100%" }}>
118
+ <Space direction="vertical" style={{ width: "100%", paddingTop: 16 }}>
119
119
  <Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
120
120
  {t("system:users.create")}
121
121
  </Button>