@oj-module/cli 0.2.68 → 0.2.71

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 (153) hide show
  1. package/devkit/README.md +10 -8
  2. package/devkit/framework-dev.md +1 -1
  3. package/devkit/manual.md +45 -19
  4. package/devkit/module-dev-and-cdn-playground.md +20 -14
  5. package/devkit/publish-walkthrough.md +132 -43
  6. package/devkit/wecom-customer-reauth-guide.md +110 -0
  7. package/package.json +2 -2
  8. package/shell-dist/assets/{runtime.1xzahckt10.js → runtime.7x99hmm020.js} +2 -2
  9. package/shell-dist/bundles/oj-market-0.2.0.tgz +0 -0
  10. package/shell-dist/bundles/oj-market-0.2.0.tgz.sig +8 -0
  11. package/shell-dist/index.html +4 -4
  12. package/shell-dist/versions.json +3 -3
  13. package/src/__tests__/list.test.ts +7 -7
  14. package/src/__tests__/oj-health-host.test.ts +19 -0
  15. package/src/__tests__/site-tenant-header.test.ts +2 -2
  16. package/src/__tests__/templates-builtin-list.test.ts +47 -0
  17. package/src/__tests__/templates-schema-columns.test.ts +11 -2
  18. package/src/__tests__/templates-tenant-scope.test.ts +2 -3
  19. package/src/__tests__/users-roles-display.test.ts +84 -0
  20. package/src/bundle.ts +1 -1
  21. package/src/contract/__tests__/market-client.integration.test.ts +23 -22
  22. package/src/enable.ts +2 -2
  23. package/src/init.ts +45 -0
  24. package/src/install.ts +5 -5
  25. package/src/lifecycle/__tests__/install.test.ts +29 -29
  26. package/src/lifecycle/__tests__/market-bundle.test.ts +87 -0
  27. package/src/lifecycle/__tests__/publish.test.ts +8 -10
  28. package/src/lifecycle/__tests__/trust.test.ts +14 -14
  29. package/src/lifecycle/__tests__/uninstall.test.ts +4 -4
  30. package/src/list.ts +3 -3
  31. package/src/market/__tests__/bootstrap.test.ts +24 -7
  32. package/src/market/__tests__/failure-playbook.test.ts +0 -1
  33. package/src/market/__tests__/remote-push.test.ts +228 -0
  34. package/src/market/__tests__/verify.test.ts +45 -207
  35. package/src/market/bootstrap.ts +12 -7
  36. package/src/market/command.ts +33 -35
  37. package/src/market/db-seed.ts +129 -0
  38. package/src/market/failure-playbook.ts +3 -9
  39. package/src/market/fixture.ts +2 -6
  40. package/src/market/publish.ts +145 -89
  41. package/src/market/remote-push.ts +236 -0
  42. package/src/market/trial.ts +2 -2
  43. package/src/market/verify.ts +17 -270
  44. package/src/market/walkthrough.ts +38 -63
  45. package/src/oj.ts +13 -2
  46. package/src/preview.ts +5 -3
  47. package/src/publish.ts +3 -2
  48. package/src/scaffold-guard.ts +4 -0
  49. package/src/site.ts +1 -1
  50. package/src/trust.ts +8 -8
  51. package/src/uninstall.ts +2 -2
  52. package/src/usage.ts +23 -21
  53. package/templates/config.yaml +22 -3
  54. package/templates/global.d.ts +6 -4
  55. package/templates/init.include.yaml +16 -11
  56. package/templates/openapi.json +3971 -3663
  57. package/templates/src/.ojm-builtin.json +13 -0
  58. package/templates/src/README.md +5 -2
  59. package/templates/src/app/api/_platform/README.md +3 -2
  60. package/templates/src/app/api/_platform/_lifecycle/atomic.ts +7 -2
  61. package/templates/src/app/api/_platform/_lifecycle/bundle-converge.ts +158 -0
  62. package/templates/src/app/api/_platform/_lifecycle/history.ts +3 -2
  63. package/templates/src/app/api/_platform/_lifecycle/installer.ts +25 -5
  64. package/templates/src/app/api/_platform/_lifecycle/local-archive.ts +61 -0
  65. package/templates/src/app/api/_platform/_lifecycle/manifest-of-upload.ts +62 -0
  66. package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +12 -2
  67. package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +4 -1
  68. package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +8 -2
  69. package/templates/src/app/api/_platform/_lifecycle/types.ts +1 -1
  70. package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +14 -5
  71. package/templates/src/app/api/_platform/_shared/service/domain/users.ts +7 -1
  72. package/templates/src/app/api/_platform/_shared/site-facade.ts +31 -0
  73. package/templates/src/app/api/_platform/manifest.yaml +1 -2
  74. package/templates/src/app/api/_platform/migrations/0003__market_catalog.mysql.sql +6 -0
  75. package/templates/src/app/api/_platform/migrations/0003__market_catalog.pg.sql +6 -0
  76. package/templates/src/app/api/_platform/migrations/0003__market_catalog.sql +18 -0
  77. package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.mysql.sql +6 -0
  78. package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.pg.sql +6 -0
  79. package/templates/src/app/api/_platform/migrations/0004__market_drop_source_seq.sql +6 -0
  80. package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.mysql.sql +8 -0
  81. package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.pg.sql +8 -0
  82. package/templates/src/app/api/_platform/migrations/0005__market_sig_registry.sql +8 -0
  83. package/templates/src/app/api/_platform/schema.yaml +8 -11
  84. package/templates/src/app/api/_platform/seed.mysql.sql +1 -1
  85. package/templates/src/app/api/_platform/seed.pg.sql +1 -1
  86. package/templates/src/app/api/_platform/seed.sql +1 -1
  87. package/templates/src/app/api/auth/_shared/wecom/sync.ts +6 -0
  88. package/templates/src/app/api/system/users/api.ts +3 -0
  89. package/templates/src/app/web/_platform/__client/api.schemas.ts +0 -113
  90. package/templates/src/app/web/_platform/__client/index.ts +1 -333
  91. package/templates/src/app/web/system/__client/api.schemas.ts +2 -0
  92. package/templates/src/app/web/system/locales/en-US.json +15 -1
  93. package/templates/src/app/web/system/locales/zh-CN.json +15 -1
  94. package/templates/src/app/web/system/pages/departments/sync-bar.tsx +9 -3
  95. package/templates/src/app/web/system/pages/roles/index.tsx +4 -8
  96. package/templates/src/app/web/system/pages/roles/role-label.ts +26 -0
  97. package/templates/src/app/web/system/pages/solutions/index.tsx +110 -0
  98. package/templates/src/app/web/system/pages/solutions/market-entry.ts +46 -0
  99. package/templates/src/app/web/system/pages/users/index.tsx +12 -2
  100. package/templates/src/app/web/system/pages/users/match-keyword.ts +19 -0
  101. package/templates/src/app/web/system/pages/users/roles.tsx +43 -14
  102. package/templates/src/app/web/system/routes.ts +18 -1
  103. package/templates/src/{app/api/_platform/market → oj-market/api}/api.ts +475 -592
  104. package/templates/src/oj-market/api/backfill.ts +221 -0
  105. package/templates/src/{app/api/_platform/market → oj-market/api}/bootstrap.ts +2 -2
  106. package/templates/src/oj-market/api/cdn/api.ts +312 -0
  107. package/templates/src/oj-market/api/cdn/shared.ts +104 -0
  108. package/templates/src/oj-market/api/cdn/signed-auth.ts +75 -0
  109. package/templates/src/oj-market/api/manifest.yaml +8 -0
  110. package/templates/src/oj-market/solution.yaml +21 -0
  111. package/templates/src/oj-market/web/__client/api.schemas.ts +167 -0
  112. package/templates/src/oj-market/web/__client/index.ts +509 -0
  113. package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +6 -6
  114. package/templates/src/oj-market/web/api/client.ts +6 -6
  115. package/templates/src/oj-market/web/api/defaultClient.ts +31 -25
  116. package/templates/src/oj-market/web/entry.ts +5 -3
  117. package/templates/src/oj-market/web/hooks/useMarket.ts +6 -6
  118. package/templates/src/oj-market/web/pages/MarketApp.tsx +20 -3
  119. package/templates/src/oj-market/web/publish/locales/en-US.json +1 -1
  120. package/templates/src/oj-market/web/publish/locales/zh-CN.json +1 -1
  121. package/templates/src/oj-market/web/publish/pages/history.tsx +2 -11
  122. package/templates/src/oj-market/web/routes.ts +12 -0
  123. package/templates/src/oj-market/web/types.ts +14 -8
  124. package/templates/tests/lifecycle/anonymous-wiring.spec.ts +65 -0
  125. package/templates/tests/lifecycle/backfill.spec.ts +392 -0
  126. package/templates/tests/lifecycle/bootstrap.spec.ts +1 -1
  127. package/templates/tests/lifecycle/bundle-converge.spec.ts +264 -0
  128. package/templates/tests/lifecycle/cdn-upload.spec.ts +661 -0
  129. package/templates/tests/lifecycle/deploy-smoke.spec.ts +25 -0
  130. package/templates/tests/lifecycle/exec-grant.spec.ts +38 -0
  131. package/templates/tests/lifecycle/installer.spec.ts +36 -0
  132. package/templates/tests/lifecycle/market-api.spec.ts +448 -803
  133. package/templates/tests/lifecycle/preflight.spec.ts +18 -0
  134. package/templates/tests/lifecycle/trust.spec.ts +2 -2
  135. package/templates/tests/lifecycle/uninstall.spec.ts +36 -2
  136. package/templates/tests/lifecycle/verify-checks.spec.ts +1 -1
  137. package/templates/tests/rbac-users.spec.ts +8 -0
  138. package/templates/tests/solutions-entry.spec.ts +55 -0
  139. package/templates/tsconfig.paths.json +1 -0
  140. package/templates/types/stream-probes/stream-handlers.ts +34 -0
  141. package/templates/web.config.ts +6 -6
  142. package/vendor/host-versions.json +3 -3
  143. package/bin/oj +0 -4
  144. package/src/market/__tests__/build-index.test.ts +0 -641
  145. package/src/market/__tests__/sharding.test.ts +0 -90
  146. package/src/market/__tests__/yank-ledger.test.ts +0 -60
  147. package/src/market/build-index.ts +0 -526
  148. package/src/market/index-builder.ts +0 -149
  149. package/src/market/sharding.ts +0 -94
  150. package/src/market/yank-ledger.ts +0 -91
  151. package/templates/src/app/api/_platform/_lifecycle/source-seq.ts +0 -88
  152. package/templates/src/app/api/_platform/market/index-source.ts +0 -112
  153. package/templates/tests/lifecycle/source-seq.spec.ts +0 -55
@@ -0,0 +1,46 @@
1
+ // 方案安装入口的纯推导(Phase B T3):页面只做 query + 渲染,状态口径收这里,
2
+ // 与 roles/visible-roles 同范式(展示层逻辑可单测,不入 React 组件)。
3
+ // 数据源:GET /api/oj-market/index 的行(superadmin;R29 第一方编排面,平台内置)。
4
+
5
+ /** index 行中本入口消费的最小形状(完整行见 oj-market api IndexRow,catchall 契约) */
6
+ export interface SolutionsRow {
7
+ name: string
8
+ latest: string | null
9
+ installed: string | null
10
+ upgradable: boolean
11
+ }
12
+
13
+ export type MarketEntry = { kind: "absent" } | { kind: "installable", latest: string } | { kind: "installed", installed: string, latest: string | null, upgradable: boolean };
14
+
15
+ /** 市场包入口状态:目录无行(出厂未装且未回填)/ 可安装 / 已装(含可升级位) */
16
+ export function marketEntryOf(list: SolutionsRow[]): MarketEntry {
17
+ const row = list.find(r => r.name === "oj-market");
18
+ if (row === undefined)
19
+ return { kind: "absent" };
20
+ if (row.installed === null)
21
+ return row.latest === null ? { kind: "absent" } : { kind: "installable", latest: row.latest };
22
+ return { kind: "installed", installed: row.installed, latest: row.latest, upgradable: row.upgradable };
23
+ }
24
+
25
+ /** 任务记录的最小消费形状(TaskRecord 真源:api/_platform/_lifecycle/types.ts) */
26
+ export interface TaskLike {
27
+ status: string
28
+ progress: number
29
+ message: string
30
+ }
31
+
32
+ export type TaskOutcome = { kind: "running", progress: number, message: string } | { kind: "done" } | { kind: "needsTrust" } | { kind: "failed", message: string };
33
+
34
+ /** 任务终态 → 入口渲染口径:needs_trust 是非成功态(TOFU 确认前不得绿) */
35
+ export function taskOutcomeOf(rec: TaskLike): TaskOutcome {
36
+ switch (rec.status) {
37
+ case "running":
38
+ return { kind: "running", progress: rec.progress, message: rec.message };
39
+ case "done":
40
+ return { kind: "done" };
41
+ case "needs_trust":
42
+ return { kind: "needsTrust" };
43
+ default:
44
+ return { kind: "failed", message: rec.message };
45
+ }
46
+ }
@@ -14,6 +14,7 @@ import { buildTreeData, subtreeIds } from "../departments/tree";
14
14
  import { explainError, isForbidden } from "../errors";
15
15
  import { ForbiddenPanel } from "../forbidden";
16
16
  import { canManageUser } from "./can-manage";
17
+ import { matchUserKeyword } from "./match-keyword";
17
18
 
18
19
  type UserRow = SystemUsersGetData[number];
19
20
 
@@ -42,6 +43,7 @@ export default function UsersPage() {
42
43
  const [pwdTarget, setPwdTarget] = useState<UserRow | null>(null);
43
44
  const [pwdForm] = Form.useForm<{ password: string }>();
44
45
  const [deptId, setDeptId] = useState<number | null>(null);
46
+ const [keyword, setKeyword] = useState("");
45
47
 
46
48
  const usersQuery = useQuery({ queryKey: ["system", "users"], queryFn: systemUsersGet });
47
49
  // 与部门页共用同一份缓存键:那边改完部门,这边的筛选树也跟着新
@@ -83,8 +85,9 @@ export default function UsersPage() {
83
85
  const scope = useMemo(() => (deptId == null ? null : subtreeIds(deptRows, [deptId])), [deptRows, deptId]);
84
86
  const visibleUsers = useMemo(() => {
85
87
  const all = usersQuery.data ?? [];
86
- return scope ? all.filter(u => u.departments.some(d => scope.has(d.id))) : all;
87
- }, [usersQuery.data, scope]);
88
+ const scoped = scope ? all.filter(u => u.departments.some(d => scope.has(d.id))) : all;
89
+ return scoped.filter(u => matchUserKeyword(u, keyword));
90
+ }, [usersQuery.data, scope, keyword]);
88
91
 
89
92
  if (usersQuery.error && isForbidden(usersQuery.error))
90
93
  return <ForbiddenPanel t={t} error={usersQuery.error} />;
@@ -158,6 +161,13 @@ export default function UsersPage() {
158
161
  <Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
159
162
  {t("system:users.create")}
160
163
  </Button>
164
+ <Input
165
+ allowClear
166
+ style={{ width: 220 }}
167
+ placeholder={t("system:users.searchPlaceholder")}
168
+ value={keyword}
169
+ onChange={e => setKeyword(e.target.value)}
170
+ />
161
171
  <TreeSelect
162
172
  allowClear
163
173
  showSearch
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 用户列表关键词搜索(纯函数,BDD 用例见 cli __tests__/users-roles-display.test.ts)。
3
+ *
4
+ * 口径(20261106-1811 计划文档):users 表无 nickname 列,「昵称与 display」按
5
+ * username(账号)+ display_name(显示名/昵称)匹配,大小写不敏感;空白关键字 = 全可见。
6
+ * 名单一页全量,过滤在前端做(与部门筛选同口径)。
7
+ */
8
+ export interface UserKeywordRow {
9
+ username: string
10
+ display_name: string | null
11
+ }
12
+
13
+ export function matchUserKeyword(row: UserKeywordRow, keyword: string): boolean {
14
+ const kw = keyword.trim().toLowerCase();
15
+ if (!kw)
16
+ return true;
17
+ const tail = row.display_name ?? "";
18
+ return row.username.toLowerCase().includes(kw) || tail.toLowerCase().includes(kw);
19
+ }
@@ -1,14 +1,16 @@
1
+ import type { SystemRolesGetData } from "../../__client";
1
2
  import { useUserStore } from "@oj-module/runtime";
2
3
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
- import { App, Button, Card, Checkbox, Space } from "antd";
4
- import { useState } from "react";
4
+ import { App, Button, Card, Checkbox, Divider, Space, Typography } from "antd";
5
+ import { useMemo, useState } from "react";
6
+
5
7
  import { useTranslation } from "react-i18next";
6
8
 
7
9
  import { useNavigate, useParams } from "react-router";
8
-
9
10
  import { systemRolesGet, systemUsersGet, systemUsersRolesPut } from "../../__client";
10
11
  import { explainError, isForbidden } from "../errors";
11
12
  import { ForbiddenPanel } from "../forbidden";
13
+ import { roleLabel } from "../roles/role-label";
12
14
 
13
15
  /**
14
16
  * 成员资格(AC1-7):整集替换某用户角色。后端无 GET 单用户端点,
@@ -18,6 +20,9 @@ import { ForbiddenPanel } from "../forbidden";
18
20
  * 授予 admin 已下放到租户内,否则企业租户永远配不出管理员(平台 superadmin 因租户
19
21
  * 隔离查不到企业租户的用户)。但 **superadmin 仍只许 superadmin 授**——它握着跨租户
20
22
  * 权限,故这里对非 superadmin 直接把该选项置灰,比让他提交后吃 403 清楚。
23
+ *
24
+ * 展示(20261106-1811):角色名按语言名称(key)解析(与角色页共用 roleLabel),
25
+ * 并按模块分组——角色行自带 module 字段,组标题显示模块名原文。
21
26
  */
22
27
  export default function UserRolesPage() {
23
28
  const { t } = useTranslation();
@@ -35,6 +40,18 @@ export default function UserRolesPage() {
35
40
  const usersQuery = useQuery({ queryKey: ["system", "users"], queryFn: systemUsersGet });
36
41
  const rolesQuery = useQuery({ queryKey: ["system", "roles"], queryFn: systemRolesGet });
37
42
 
43
+ // 按模块分组(组内保持 listRoles 的 name 排序);模块名原文作组标题。
44
+ // 位置在提前 return 之前——hooks 不得落在条件分支里
45
+ const groups = useMemo(() => {
46
+ const byModule = new Map<string, SystemRolesGetData>();
47
+ for (const r of rolesQuery.data ?? []) {
48
+ const list = byModule.get(r.module) ?? [];
49
+ list.push(r);
50
+ byModule.set(r.module, list);
51
+ }
52
+ return [...byModule.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
53
+ }, [rolesQuery.data]);
54
+
38
55
  const saveMut = useMutation({
39
56
  mutationFn: (roles: string[]) => systemUsersRolesPut({ query: { uid: Number(uid) }, body: { roles } }),
40
57
  onSuccess: () => {
@@ -55,18 +72,30 @@ export default function UserRolesPage() {
55
72
 
56
73
  return (
57
74
  <Card title={`${t("system:userRoles.title")}: ${user?.username ?? `#${uid}`}`}>
58
- <Space direction="vertical">
59
- <Checkbox.Group
60
- options={(rolesQuery.data ?? []).map(r => ({
61
- label: `${r.name}${r.deprecated ? t("system:userRoles.deprecated") : ""}`,
62
- value: r.name,
63
- // 非 superadmin 授不了平台级角色:置灰,提交后 403 太晚也太含糊
64
- disabled: r.name === "superadmin" && !isSuper,
65
- }))}
66
- value={value}
67
- onChange={v => setSelected(v)}
68
- />
75
+ <Space direction="vertical" style={{ width: "100%" }}>
76
+ {groups.map(([mod, rows]) => {
77
+ const names = new Set(rows.map(r => r.name));
78
+ return (
79
+ <div key={mod}>
80
+ <Typography.Text strong>{mod}</Typography.Text>
81
+ <div style={{ paddingLeft: 12, paddingTop: 4 }}>
82
+ <Checkbox.Group
83
+ options={rows.map(r => ({
84
+ label: `${roleLabel(r, t)}${r.deprecated ? t("system:userRoles.deprecated") : ""}`,
85
+ value: r.name,
86
+ // 非 superadmin 授不了平台级角色:置灰,提交后 403 太晚也太含糊
87
+ disabled: r.name === "superadmin" && !isSuper,
88
+ }))}
89
+ // 整集替换语义不变:每组只回显/改动本组名下的选中项
90
+ value={value.filter(v => names.has(v))}
91
+ onChange={vals => setSelected([...value.filter(v => !names.has(v)), ...vals])}
92
+ />
93
+ </div>
94
+ </div>
95
+ );
96
+ })}
69
97
  {value.length === 0 ? <span>{t("system:userRoles.none")}</span> : null}
98
+ <Divider style={{ margin: "8px 0" }} />
70
99
  <Button
71
100
  type="primary"
72
101
  disabled={isSelf || selected === null}
@@ -4,6 +4,10 @@ import { SettingOutlined } from "@ant-design/icons";
4
4
 
5
5
  import { createElement, lazy } from "react";
6
6
 
7
+ // 方案安装入口(Phase B T3):平台内置编排面 /api/oj-market/*(api 随脚手架内置,R29
8
+ // 第一方专用)由 system 域代管。用生成物明示的 bindRequest 逃生门而非
9
+ // createOjMarketClient 工厂——后者 DEV 有跨模块认领告警(该 client 名义属 oj-market)。
10
+ import { bindRequest as bindMarketRequest } from "../../../oj-market/web/__client";
7
11
  import { createSystemClient } from "./__client";
8
12
 
9
13
  const Users = lazy(() => import("./pages/users"));
@@ -13,6 +17,7 @@ const Roles = lazy(() => import("./pages/roles"));
13
17
  const Oplog = lazy(() => import("./pages/oplog"));
14
18
  const Settings = lazy(() => import("./pages/settings"));
15
19
  const WecomAdmin = lazy(() => import("./pages/wecom"));
20
+ const Solutions = lazy(() => import("./pages/solutions"));
16
21
 
17
22
  /**
18
23
  * `/system` 根的角色门禁(对齐后端 assertSystemAdmin,access.ts:87)——
@@ -75,6 +80,12 @@ export const routes: AppRouteRecordRaw[] = [
75
80
  Component: Settings,
76
81
  handle: { title: "system:menu.settings", roles: ["superadmin"] },
77
82
  },
83
+ {
84
+ // 方案安装(Phase B T3,出厂未装态的首装入口):仅超管可见可访问
85
+ path: "/system/solutions",
86
+ Component: Solutions,
87
+ handle: { title: "system:menu.solutions", roles: ["superadmin"] },
88
+ },
78
89
  {
79
90
  // 企微代开发后台(服务商运营面):仅超管可见(路由级 roles + 页面内二次判定)
80
91
  path: "/system/wecom",
@@ -85,7 +96,13 @@ export const routes: AppRouteRecordRaw[] = [
85
96
  },
86
97
  ];
87
98
 
88
- /** 构造即接线:登记 /system 前缀 + 绑定 scoped request(D11 前缀收敛)。 */
99
+ /**
100
+ * 构造即接线:登记 /system 前缀 + 绑定 scoped request(D11 前缀收敛);
101
+ * 另登记 /oj-market(平台内置市场编排面,R29 第一方)并绑定其生成 client——
102
+ * 方案安装页(/system/solutions)出厂未装态即经此调用安装端点。
103
+ */
89
104
  export async function onInit(ctx: ModuleContext) {
90
105
  createSystemClient(ctx);
106
+ ctx.register.apiPrefix("/oj-market");
107
+ bindMarketRequest(ctx.utils.request);
91
108
  }