@oj-module/cli 0.2.66 → 0.2.67

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 (187) hide show
  1. package/bin/.oj-version +1 -0
  2. package/bin/oj +4 -0
  3. package/devkit/README.md +15 -7
  4. package/devkit/SKILL.md +30 -1
  5. package/devkit/framework-dev.md +10 -4
  6. package/devkit/manual.md +38 -12
  7. package/devkit/publish-walkthrough.md +110 -37
  8. package/devkit/wecom-internal-app.md +7 -2
  9. package/devkit/wecom-multi-corp-links.md +163 -0
  10. package/package.json +2 -2
  11. package/shell-dist/assets/{index.i0kqbxp050.js → index.6alqyx6ae0.js} +21 -1
  12. package/shell-dist/assets/{runtime.1xfsxcajj0.js → runtime.2dgstpjsu0.js} +3449 -3449
  13. package/shell-dist/index.html +4 -4
  14. package/shell-dist/versions.json +3 -3
  15. package/src/__tests__/publish-keygen.test.ts +157 -0
  16. package/src/__tests__/templates-migration-schema-parity.test.ts +540 -0
  17. package/src/__tests__/templates-schema-columns.test.ts +15 -2
  18. package/src/__tests__/templates-tenant-scope.test.ts +6 -2
  19. package/src/bundle.ts +8 -3
  20. package/src/cli.ts +17 -5
  21. package/src/dev.ts +15 -10
  22. package/src/install.ts +11 -4
  23. package/src/keygen.ts +54 -0
  24. package/src/lifecycle/__tests__/bundle.test.ts +21 -3
  25. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +1 -1
  26. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +2 -2
  27. package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +5 -2
  28. package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +1 -1
  29. package/src/lifecycle/__tests__/manifest-contract.test.ts +75 -3
  30. package/src/lifecycle/__tests__/smoke.test.ts +2 -2
  31. package/src/manifest-contract.ts +91 -8
  32. package/src/market/bootstrap.ts +26 -2
  33. package/src/market/command.ts +1 -0
  34. package/src/market/publish.ts +5 -1
  35. package/src/market/walkthrough.ts +24 -8
  36. package/src/site.ts +2 -0
  37. package/src/usage.ts +8 -4
  38. package/src/vendor.ts +5 -1
  39. package/templates/config.yaml +4 -2
  40. package/templates/init.include.yaml +8 -3
  41. package/templates/openapi.json +460 -1
  42. package/templates/src/app/api/_platform/_lifecycle/compensate.ts +136 -0
  43. package/templates/src/app/api/_platform/_lifecycle/installer.ts +71 -22
  44. package/templates/src/app/api/_platform/_lifecycle/lock.ts +23 -0
  45. package/templates/src/app/api/_platform/_lifecycle/steps/deploy-web.ts +28 -0
  46. package/templates/src/app/api/_platform/_lifecycle/steps/preflight.ts +46 -0
  47. package/templates/src/app/api/_platform/_lifecycle/steps/reload.ts +100 -36
  48. package/templates/src/app/api/_platform/_lifecycle/steps/smoke.ts +7 -15
  49. package/templates/src/app/api/_platform/_lifecycle/text-edit.ts +71 -19
  50. package/templates/src/app/api/_platform/_lifecycle/types.ts +26 -2
  51. package/templates/src/app/api/_platform/_lifecycle/uninstall.ts +93 -14
  52. package/templates/src/app/api/_platform/_shared/service/domain/settings.ts +8 -4
  53. package/templates/src/app/api/_platform/manifest.yaml +1 -0
  54. package/templates/src/app/api/_platform/market/api.ts +235 -10
  55. package/templates/src/app/api/_platform/migrations/0001__baseline.mysql.sql +178 -0
  56. package/templates/src/app/api/_platform/migrations/0001__baseline.pg.sql +173 -0
  57. package/templates/src/app/api/_platform/migrations/0001__baseline.sql +188 -0
  58. package/templates/src/app/api/_platform/migrations/0002__market_release.mysql.sql +17 -0
  59. package/templates/src/app/api/_platform/migrations/0002__market_release.pg.sql +16 -0
  60. package/templates/src/app/api/_platform/migrations/0002__market_release.sql +33 -0
  61. package/templates/src/app/api/_platform/schema.yaml +18 -0
  62. package/templates/src/app/api/_platform/seed.mysql.sql +10 -0
  63. package/templates/src/app/api/_platform/seed.pg.sql +10 -0
  64. package/templates/src/app/api/auth/_shared/wecom/client.ts +24 -4
  65. package/templates/src/app/api/home/migrations/0001__create_home_tables.mysql.sql +14 -0
  66. package/templates/src/app/api/home/migrations/0001__create_home_tables.pg.sql +14 -0
  67. package/templates/src/app/api/home/migrations/0002__tenant.pg.sql +4 -0
  68. package/templates/src/app/api/home/seed.mysql.sql +71 -0
  69. package/templates/src/app/api/home/seed.pg.sql +71 -0
  70. package/templates/src/app/api/notification/_shared/notify.ts +6 -6
  71. package/templates/src/app/api/notification/migrations/0001__create_notifications.mysql.sql +11 -0
  72. package/templates/src/app/api/notification/migrations/0001__create_notifications.pg.sql +11 -0
  73. package/templates/src/app/api/notification/migrations/0002__notifications_user.mysql.sql +8 -0
  74. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.mysql.sql +21 -0
  75. package/templates/src/app/api/notification/migrations/0003__notifications_broadcast.pg.sql +22 -0
  76. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.mysql.sql +10 -0
  77. package/templates/src/app/api/notification/migrations/0004__notifications_broadcast_hidden.pg.sql +11 -0
  78. package/templates/src/app/api/notification/migrations/0005__tenant.pg.sql +6 -0
  79. package/templates/src/app/api/notification/seed.mysql.sql +8 -0
  80. package/templates/src/app/api/notification/seed.pg.sql +11 -0
  81. package/templates/src/app/api/publish/_shared/identity.ts +69 -0
  82. package/templates/src/app/api/publish/api.ts +349 -0
  83. package/templates/src/app/api/publish/manifest.yaml +8 -0
  84. package/templates/src/app/api/publish/migrations/0001__publish_credential.mysql.sql +23 -0
  85. package/templates/src/app/api/publish/migrations/0001__publish_credential.pg.sql +23 -0
  86. package/templates/src/app/api/publish/migrations/0001__publish_credential.sql +39 -0
  87. package/templates/src/app/api/publish/schema.yaml +33 -0
  88. package/templates/src/app/api/system/login-methods/api.ts +2 -1
  89. package/templates/src/app/web/_platform/__client/api.schemas.ts +15 -0
  90. package/templates/src/app/web/_platform/__client/index.ts +25 -1
  91. package/templates/src/app/web/entry.ts +9 -5
  92. package/templates/src/app/web/home/routes.ts +5 -0
  93. package/templates/src/app/web/login/locales/en-US.json +7 -1
  94. package/templates/src/app/web/login/locales/zh-CN.json +7 -1
  95. package/templates/src/app/web/login/pages/login.tsx +79 -86
  96. package/templates/src/app/web/login/routes.ts +54 -1
  97. package/templates/src/app/web/personal-center/locales/en-US.json +1 -0
  98. package/templates/src/app/web/personal-center/locales/zh-CN.json +1 -0
  99. package/templates/src/app/web/personal-center/pages/my-profile/index.tsx +5 -0
  100. package/templates/src/app/web/system/__client/api.schemas.ts +2 -1
  101. package/templates/src/app/web/system/locales/en-US.json +1 -8
  102. package/templates/src/app/web/system/locales/zh-CN.json +1 -8
  103. package/templates/src/app/web/system/pages/settings/index.tsx +17 -4
  104. package/templates/src/app/web/system/routes.ts +0 -6
  105. package/templates/src/oj-market/web/__tests__/CardGrid.test.tsx +33 -0
  106. package/templates/src/oj-market/web/__tests__/DetailDrawer.test.tsx +69 -0
  107. package/templates/src/oj-market/web/__tests__/marketClient.fake.ts +7 -1
  108. package/templates/src/oj-market/web/api/client.ts +3 -0
  109. package/templates/src/oj-market/web/api/defaultClient.ts +5 -0
  110. package/templates/src/oj-market/web/components/CardItem.tsx +44 -1
  111. package/templates/src/oj-market/web/components/DetailDrawer.tsx +17 -1
  112. package/templates/src/oj-market/web/entry.ts +31 -2
  113. package/templates/src/oj-market/web/locales/en-US.json +5 -1
  114. package/templates/src/oj-market/web/locales/zh-CN.json +5 -1
  115. package/templates/src/oj-market/web/publish/__client/api.schemas.ts +61 -0
  116. package/templates/src/oj-market/web/publish/__client/index.ts +164 -0
  117. package/templates/src/oj-market/web/publish/locales/en-US.json +80 -0
  118. package/templates/src/oj-market/web/publish/locales/zh-CN.json +80 -0
  119. package/templates/src/oj-market/web/publish/pages/history.tsx +112 -0
  120. package/templates/src/oj-market/web/publish/pages/index.tsx +196 -0
  121. package/templates/src/oj-market/web/publish/pages/model.ts +52 -0
  122. package/templates/src/oj-market/web/publish/pages/token-modal.tsx +82 -0
  123. package/templates/src/oj-market/web/publish/routes.ts +41 -0
  124. package/templates/src/oj-market/web/types.ts +30 -0
  125. package/templates/tests/lifecycle/compensate.spec.ts +207 -0
  126. package/templates/tests/lifecycle/deploy-smoke.spec.ts +38 -14
  127. package/templates/tests/lifecycle/installer.spec.ts +28 -9
  128. package/templates/tests/lifecycle/market-api.spec.ts +164 -12
  129. package/templates/tests/lifecycle/preflight.spec.ts +68 -0
  130. package/templates/tests/lifecycle/role-title-translate.spec.ts +2 -32
  131. package/templates/tests/lifecycle/text-edit.spec.ts +45 -27
  132. package/templates/tests/lifecycle/uninstall.spec.ts +36 -0
  133. package/templates/tests/publish-credential.spec.ts +354 -0
  134. package/templates/tests/publish-identity.spec.ts +44 -0
  135. package/templates/tests/wecom-client-timeout.spec.ts +33 -3
  136. package/templates/tests/wecom-landing.spec.ts +63 -1
  137. package/templates/tsconfig.paths.json +1 -0
  138. package/templates/web.config.ts +6 -4
  139. package/vendor/host-versions.json +3 -3
  140. package/templates/seed.mysql.sql +0 -6
  141. package/templates/seed.pg.sql +0 -6
  142. package/templates/src/app/api/_platform/migrations/0001__create_users.mysql.sql +0 -9
  143. package/templates/src/app/api/_platform/migrations/0001__create_users.pg.sql +0 -9
  144. package/templates/src/app/api/_platform/migrations/0001__create_users.sql +0 -7
  145. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.mysql.sql +0 -3
  146. package/templates/src/app/api/_platform/migrations/0002__add_users_avatar_base64.sql +0 -2
  147. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.mysql.sql +0 -5
  148. package/templates/src/app/api/_platform/migrations/0003__users_source_display_name.sql +0 -5
  149. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.mysql.sql +0 -70
  150. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.pg.sql +0 -67
  151. package/templates/src/app/api/_platform/migrations/0004__sys_rbac.sql +0 -67
  152. package/templates/src/app/api/_platform/migrations/0005__users_must_change.mysql.sql +0 -3
  153. package/templates/src/app/api/_platform/migrations/0005__users_must_change.sql +0 -2
  154. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.mysql.sql +0 -3
  155. package/templates/src/app/api/_platform/migrations/0006__users_is_forbidden.sql +0 -3
  156. package/templates/src/app/api/_platform/migrations/0007__users_email.sql +0 -3
  157. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.mysql.sql +0 -10
  158. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.pg.sql +0 -10
  159. package/templates/src/app/api/_platform/migrations/0008__sys_refresh_token.sql +0 -11
  160. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.mysql.sql +0 -12
  161. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.pg.sql +0 -12
  162. package/templates/src/app/api/_platform/migrations/0009__sys_throttle.sql +0 -13
  163. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.mysql.sql +0 -17
  164. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.pg.sql +0 -16
  165. package/templates/src/app/api/_platform/migrations/0010__sys_solution_route.sql +0 -18
  166. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.mysql.sql +0 -2
  167. package/templates/src/app/api/_platform/migrations/0011__sys_setting_group_label.sql +0 -4
  168. package/templates/src/app/api/_platform/migrations/0012__market_history.mysql.sql +0 -13
  169. package/templates/src/app/api/_platform/migrations/0012__market_history.pg.sql +0 -12
  170. package/templates/src/app/api/_platform/migrations/0012__market_history.sql +0 -14
  171. package/templates/src/app/api/_platform/migrations/0013__users_profile.sql +0 -4
  172. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.mysql.sql +0 -9
  173. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.pg.sql +0 -7
  174. package/templates/src/app/api/_platform/migrations/0014__reg_source_seq.sql +0 -8
  175. package/templates/src/app/api/_platform/migrations/0015__tenant.mysql.sql +0 -12
  176. package/templates/src/app/api/_platform/migrations/0015__tenant.pg.sql +0 -11
  177. package/templates/src/app/api/_platform/migrations/0015__tenant.sql +0 -44
  178. package/templates/src/app/api/_platform/migrations/0016__org.mysql.sql +0 -41
  179. package/templates/src/app/api/_platform/migrations/0016__org.pg.sql +0 -40
  180. package/templates/src/app/api/_platform/migrations/0016__org.sql +0 -62
  181. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +0 -7
  182. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +0 -4
  183. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +0 -8
  184. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.mysql.sql +0 -3
  185. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.pg.sql +0 -3
  186. package/templates/src/app/api/_platform/migrations/0018__sys_role_title_key.sql +0 -8
  187. package/templates/src/app/web/system/pages/login-methods/index.tsx +0 -30
@@ -70,6 +70,17 @@ export interface WalkthroughResult {
70
70
  port: number
71
71
  }
72
72
 
73
+ /**
74
+ * 安装成功终态:oj 0.1.122 起,无守护形态安装会落 partial(vars/auth 等冻结段热改属
75
+ * restart_pending,需进程重启才完全生效)——这是正确终态(P0 §3.6 / 验收 15),不是失败。
76
+ * 真正的失败 partial(锁后异常 + 补偿回滚)不会带 restartRequired,故以它区分二者。
77
+ * 本 walkthrough 在每次安装后置重启(IsolatedSite.restart),模拟真实「部署 → 重启」时序,
78
+ * 让模块真正上线。
79
+ */
80
+ function installSucceeded(task: TaskRecord): boolean {
81
+ return task.status === "done" || (task.status === "partial" && task.restartRequired === true);
82
+ }
83
+
73
84
  /**
74
85
  * neg-* 专用版本:与主链路(0.1.0/0.2.0)完全隔离,收尾整包摘除。
75
86
  * 必须低于主链路 latest(0.2.0),否则发布后它会成为 latest,摘除时 REG-CS2 单调性守卫
@@ -96,8 +107,8 @@ const OPS: Op[] = [
96
107
  title: "安装(11 段)",
97
108
  run: async (ctx) => {
98
109
  const task = await install(ctx, ctx.version);
99
- const done = task.status === "done";
100
- return { ok: done, detail: done ? `${task.stages.length} 段全 ok` : `status=${task.status} stage=${task.stage ?? "?"}:${task.message}` };
110
+ const ok = installSucceeded(task);
111
+ return { ok, detail: ok ? `${task.stages.length} 段全 ok(终态 ${task.status}${task.restartRequired ? " + restartRequired" : ""},已重启激活)` : `status=${task.status} stage=${task.stage ?? "?"}:${task.message}` };
101
112
  },
102
113
  },
103
114
  {
@@ -106,7 +117,7 @@ const OPS: Op[] = [
106
117
  run: async (ctx) => {
107
118
  await ensureInstalled(ctx);
108
119
  const task = await install(ctx, ctx.version);
109
- const ok = task.status === "done";
120
+ const ok = installSucceeded(task);
110
121
  return { ok, detail: ok ? "重装成功(未报方案名占用)" : `status=${task.status}:${task.message}` };
111
122
  },
112
123
  },
@@ -159,7 +170,7 @@ const OPS: Op[] = [
159
170
  await ensureInstalled(ctx); // 前置:0.1.0 在列(账本 head=1)
160
171
  await publishVersion(ctx, "0.2.0");
161
172
  const task = await install(ctx, "0.2.0");
162
- if (task.status !== "done")
173
+ if (!installSucceeded(task))
163
174
  return { ok: false, detail: `升级安装未 done:status=${task.status} stage=${task.stage ?? "?"}:${task.message}` };
164
175
  const card = await indexCard(ctx);
165
176
  const distDir = resolveLayout(ctx.site.root).apiDist;
@@ -189,7 +200,7 @@ const OPS: Op[] = [
189
200
  if (card?.installed !== "0.2.0") {
190
201
  await publishVersion(ctx, "0.2.0");
191
202
  const pre = await install(ctx, "0.2.0");
192
- if (pre.status !== "done")
203
+ if (!installSucceeded(pre))
193
204
  return { ok: false, detail: `前置安装 0.2.0 未 done:status=${pre.status}:${pre.message}` };
194
205
  card = await indexCard(ctx);
195
206
  }
@@ -320,7 +331,7 @@ const OPS: Op[] = [
320
331
  return { ok: false, detail: `未停 needs_trust:status=${first.status}:${first.message}` };
321
332
  await callApi(fetch, ctx.base, ctx.token, "POST", "/api/_platform/market/trust", { task_id: first.id });
322
333
  const second = await installOnce(ctx, NEG_VERSION, NEG_NAME);
323
- if (second.status !== "done")
334
+ if (!installSucceeded(second))
324
335
  return { ok: false, detail: `确认后重装未 done:status=${second.status}:${second.message}` };
325
336
  // 收尾(F3):显式 revoke 陌生 key,快照 diff 断言无残留——不靠 uninstall 兜底(它本就不摘信任)
326
337
  const del = await callApi<{ revoked?: boolean }>(fetch, ctx.base, ctx.token, "DELETE", `/api/_platform/market/trust/key?key_id=${encodeURIComponent("stranger.publisher")}`);
@@ -417,13 +428,18 @@ export async function walkthrough(opts: WalkthroughOptions = {}): Promise<Walkth
417
428
  }
418
429
  }
419
430
 
420
- /** 安装 + 轮询;needs_trust 时自动确认(隔离站点,无外部信任可言) */
431
+ /**
432
+ * 安装 + 轮询;needs_trust 时自动确认(隔离站点,无外部信任可言)。
433
+ * 成功(done 或 restartRequired 的 partial)后置重启,激活冻结段,模拟真实部署时序。
434
+ */
421
435
  async function install(ctx: Ctx, version: string): Promise<TaskRecord> {
422
436
  let task = await installOnce(ctx, version);
423
437
  if (task.status === "needs_trust") {
424
438
  await callApi(fetch, ctx.base, ctx.token, "POST", "/api/_platform/market/trust", { task_id: task.id });
425
439
  task = await installOnce(ctx, version);
426
440
  }
441
+ if (installSucceeded(task))
442
+ await ctx.site.restart();
427
443
  return task;
428
444
  }
429
445
 
@@ -462,7 +478,7 @@ async function ensureInstalled(ctx: Ctx): Promise<void> {
462
478
  if (card?.installed === ctx.version)
463
479
  return;
464
480
  const task = await install(ctx, ctx.version);
465
- if (task.status !== "done")
481
+ if (!installSucceeded(task))
466
482
  throw new Error(`前置安装失败:status=${task.status}:${task.message}`);
467
483
  }
468
484
 
package/src/site.ts CHANGED
@@ -43,6 +43,8 @@ export interface TaskRecord {
43
43
  appliedSeq?: number
44
44
  startedAt: number
45
45
  finishedAt?: number
46
+ /** 无守护形态部分生效:冻结段(auth/vars)热改属 restart_pending,需宿主重启才完全生效 */
47
+ restartRequired?: boolean
46
48
  }
47
49
 
48
50
  /** 模板 serve 缺省端口(templates/config.yaml server.port: 9778;host 显式钉 127.0.0.1) */
package/src/usage.ts CHANGED
@@ -245,11 +245,13 @@ export const HELP_GROUPS: HelpGroup[] = [
245
245
  { raw: "--name <name>", zh: "覆盖文件名推导的方案名", en: "Override the solution name derived from the file name" },
246
246
  { raw: "--version <version>", zh: "覆盖文件名推导的版本", en: "Override the version derived from the file name" },
247
247
  { raw: "--registry <url>", zh: "从指定 registry 源安装(白名单在服务端;旧站点 API < 0.2.0 不支持)", en: "Install from the specified registry source (allowlist is server-side; sites with API < 0.2.0 are unsupported)" },
248
+ { raw: "--force", zh: "强制重装配(oj 0.1.123+ reloadConfig(force)):旁路 unchanged 幂等短路,强制宿主重做一次装配", en: "Force re-assembly (oj 0.1.123+ reloadConfig(force)): bypass the unchanged idempotent short-circuit and force a re-assembly" },
248
249
  { raw: "--locale <locale>", zh: "终态文案语言 zh-CN|en-US", en: "Language of the final status message zh-CN|en-US" },
250
+ { raw: "--force", zh: "强制重装配(绕过「unchanged=已是最新装配」幂等短路,要求宿主重跑一次 reload;oj ≥ v0.1.123)", en: "Force a reload even if unchanged (bypass the idempotent short-circuit; requires oj ≥ v0.1.123)" },
249
251
  ],
250
252
  detail: {
251
- zh: "ojm install <file.tgz | name[@version] | @<scope>/<name>[@version]> [--site-url <u>] [--user <u>] [--pass <p>] [--name <n>] [--version <v>] [--registry <r>] [--locale <zh-CN|en-US>]\n 本地 tgz 走 file 上传(<name>-<version>.tgz 文件名推导身份,可 --name/--version 覆盖);name[@version] 走 CDN 市场源;@<scope>/<name>[@version] --registry <url> 走外部 registry 源(目录键由 CLI 归一为物理名 slug,白名单在服务端)\n 登录 → 提交 → 轮询任务至终态并打印结果(失败附原因 + 指引)\n 站点寻址:--site-url/--user/--pass,或环境变量 OJM_SITE_URL / OJM_USER / OJM_PASS",
252
- en: "ojm install <file.tgz | name[@version] | @<scope>/<name>[@version]> [--site-url <u>] [--user <u>] [--pass <p>] [--name <n>] [--version <v>] [--registry <r>] [--locale <zh-CN|en-US>]\n Local tgz goes through file upload (identity derived from the <name>-<version>.tgz file name, overridable via --name/--version); name[@version] goes through the CDN marketplace; @<scope>/<name>[@version] --registry <url> goes through an external registry source (the directory key is normalized to the physical slug by the CLI; allowlist is server-side)\n Login → submit → poll the task to a terminal state and print the result (failures include the reason + guidance)\n Site addressing: --site-url/--user/--pass, or env vars OJM_SITE_URL / OJM_USER / OJM_PASS",
253
+ zh: "ojm install <file.tgz | name[@version] | @<scope>/<name>[@version]> [--site-url <u>] [--user <u>] [--pass <p>] [--name <n>] [--version <v>] [--registry <r>] [--force] [--locale <zh-CN|en-US>]\n 本地 tgz 走 file 上传(<name>-<version>.tgz 文件名推导身份,可 --name/--version 覆盖);name[@version] 走 CDN 市场源;@<scope>/<name>[@version] --registry <url> 走外部 registry 源(目录键由 CLI 归一为物理名 slug,白名单在服务端)\n 登录 → 提交 → 轮询任务至终态并打印结果(失败附原因 + 指引)\n --force:强制重装配(oj 0.1.123+),旁路「已是最新装配」幂等短路\n 站点寻址:--site-url/--user/--pass,或环境变量 OJM_SITE_URL / OJM_USER / OJM_PASS",
254
+ en: "ojm install <file.tgz | name[@version] | @<scope>/<name>[@version]> [--site-url <u>] [--user <u>] [--pass <p>] [--name <n>] [--version <v>] [--registry <r>] [--force] [--locale <zh-CN|en-US>]\n Local tgz goes through file upload (identity derived from the <name>-<version>.tgz file name, overridable via --name/--version); name[@version] goes through the CDN marketplace; @<scope>/<name>[@version] --registry <url> goes through an external registry source (the directory key is normalized to the physical slug by the CLI; allowlist is server-side)\n Login → submit → poll the task to a terminal state and print the result (failures include the reason + guidance)\n --force: force re-assembly (oj 0.1.123+), bypassing the \"already latest assembly\" idempotent short-circuit\n Site addressing: --site-url/--user/--pass, or env vars OJM_SITE_URL / OJM_USER / OJM_PASS",
253
255
  },
254
256
  },
255
257
  {
@@ -364,12 +366,14 @@ export const HELP_GROUPS: HelpGroup[] = [
364
366
  en: "Generate an Ed25519 signing key pair",
365
367
  options: [
366
368
  { raw: "--id <id>", zh: "发布方 key id(形如 publisher.key)", en: "Publisher key id (e.g. publisher.key)" },
369
+ { raw: "--credential <token>", zh: "在线兑换发布凭证(也读 OJM_PUBLISH_CREDENTIAL;与 --id 互斥)", en: "Redeem a publishing credential online (also reads OJM_PUBLISH_CREDENTIAL; mutually exclusive with --id)" },
370
+ { raw: "--site-url <url>", zh: "兑换站点(缺省 OJM_SITE_URL / http://127.0.0.1:9778)", en: "Site for redemption (defaults to OJM_SITE_URL / http://127.0.0.1:9778)" },
367
371
  { raw: "--out <dir>", zh: "输出目录(缺省 ~/.ojm/keys/<id>/;指到工程树内须 --yes)", en: "Output directory (default ~/.ojm/keys/<id>/; pointing inside the project tree requires --yes)" },
368
372
  { raw: "--yes", zh: "确认私钥落盘到工程树内(私钥入仓即泄露)", en: "Acknowledge writing the private key inside the project tree (a private key in the repo is leaked)" },
369
373
  ],
370
374
  detail: {
371
- zh: "ojm keygen --id <publisher.id> [--out <dir>] [--yes]\n 私钥默认 ~/.ojm/keys/<id>/ 永不出目录;打印公钥 hex 供 ojm trust add / 模板回填",
372
- en: "ojm keygen --id <publisher.id> [--out <dir>] [--yes]\n The private key stays under ~/.ojm/keys/<id>/ by default; prints the public key hex for ojm trust add / template backfill",
375
+ zh: "ojm keygen --id <publisher.id> [--out <dir>] [--yes]\n 私钥默认 ~/.ojm/keys/<id>/ 永不出目录;打印公钥 hex 供 ojm trust add / 模板回填\n\nojm keygen --credential <token> [--site-url <url>] [--out <dir>] [--yes]\n 凭证兑换:key_id 由站点按「产品名.用户名」算好下发,CLI 不推导;凭证一次性,兑换即作废\n 失败(过期/已用/已撤销/网络)不落任何密钥文件;兑换成功再走与离线版完全相同的落盘流程",
376
+ en: "ojm keygen --id <publisher.id> [--out <dir>] [--yes]\n The private key stays under ~/.ojm/keys/<id>/ by default; prints the public key hex for ojm trust add / template backfill\n\nojm keygen --credential <token> [--site-url <url>] [--out <dir>] [--yes]\n Credential redemption: key_id is derived server-side as <product>.<username>, the CLI never re-derives it; the credential is single-use\n Any failure (expired/used/revoked/network) writes no key files; on success the same keygen flow as the offline path runs",
373
377
  },
374
378
  },
375
379
  {
package/src/vendor.ts CHANGED
@@ -43,8 +43,12 @@ export const NPM_PKG = "@oj-bin/oj";
43
43
  * `__client` 内省 fail-fast,`oj openapi --module-root` 契约链整体不可用)修复于
44
44
  * 0.1.86(oj-bin 0f8a6e2,第三次基线发布通道实证);module-root 工程是 0.2.20 起
45
45
  * 的默认布局,门禁必须盖住契约链(见 playbook §9 与 2026-10-23 第三次基线)。
46
+ * **0.2.68 抬到 0.1.121**:模板的方言 seed(`seed.mysql.sql` / `seed.pg.sql`)依赖
47
+ * 0.1.121 的「方言件胜出、`seed.sql` 兜底」选择语义(oj-bin 7d70b90);更早的 oj
48
+ * 只读 `seed.sql`,三方言执行同一份 sqlite 写法 → mysql/pg 上 1064 拒启。模板不再
49
+ * 教「部署期手工覆盖」,门禁必须盖住这条链路(见 playbook §11 U1)。
46
50
  */
47
- export const MIN_OJ_VERSION = "0.1.86";
51
+ export const MIN_OJ_VERSION = "0.1.121";
48
52
 
49
53
  /** 按段数值比较(字符串比较会把 0.1.9 判成大于 0.1.69);非数字段按 0 处理 */
50
54
  export function isSupportedOjVersion(version: string): boolean {
@@ -50,8 +50,6 @@ db:
50
50
  default: 'sqlite://db.sqlite' # 相对 config 目录;缺文件自动建空库
51
51
 
52
52
  vars:
53
- # ojm install 每次安装自增一次,用来触发 reload 热更。别删行、别手工回写旧值。
54
- ojm_install_seq: 0
55
53
  # 站点对外地址。装方案时服务端拿它把 /cdn 这类相对地址补全成绝对 URL(深检探针也用它)。
56
54
  # 故意不读请求头 Host(防伪造 Host 牵引出站):不配 = fail-closed,不会替你猜。
57
55
  # canonical_origin: "https://your-site.example.com"
@@ -160,6 +158,7 @@ auth:
160
158
  - /auth/wecom/callback # 企微 302 回跳:code 即凭证,此时还没有本地会话
161
159
  - /auth/wecom/ticket # 一次性票据换 TokenBundle:票据本身就是「刚验过的登录证明」
162
160
  - /auth/wecom/event # 企微指令回调:靠 msg_signature 验签,不走 Bearer
161
+ - /publish/credential/exchange # 凭证换发布身份:凭证自身即持有型证明,CLI 可能没有账号密码
163
162
 
164
163
  # 多租户(企微代开发 v3 Phase 0):开启后所有 {base} 请求必须带 X-TENANT-ID,否则 400。
165
164
  # 前端由 tenantStore + headerProvider 统一注入(单租户部署恒为 default,UX 不变)。
@@ -180,10 +179,12 @@ tenant:
180
179
  - sys_rate_window # 限速窗口(登录前就要用)
181
180
  - sys_login_throttle # 登录节流(登录前就要用)
182
181
  - market_history # 解决方案安装/升级/回滚是站点级留痕
182
+ - market_release # 发布历史:CDN 是站点级的,历史按站点归口
183
183
  - reg_source_seq # 索引新鲜度水位属于站点(防回放)
184
184
  - wecom_app_profile # 企微 corp→租户映射权威:查它时必然还没有租户上下文(D9)
185
185
  - wecom_suite_ticket # 套件级凭证,按 suite_id 全局唯一
186
186
  - wecom_login_ticket # 一次性登录票据:跨「匿名 302 回跳」与「已认证换取」两段
187
+ - publish_credential # 发布凭证:CLI 兑换那一跳没有业务租户语义,归属走 owner_tenant 列
187
188
  # 豁免缺租户头的路径(去 base 前缀后的形态)。与 auth.anonymous_paths 同语义,
188
189
  # 但这是「没有租户头也不 400」的逃生口,只给**真的拿不到头**的端点。
189
190
  # 登录类端点不在此列:前端 headerProvider 每请求都注头(单租户恒 default),
@@ -196,6 +197,7 @@ tenant:
196
197
  - /auth/wecom/callback # 企微 302 回跳:浏览器带不了头,租户由 state→profile 推导
197
198
  - /auth/wecom/ticket # 一次性票据换会话:租户由票据行携带
198
199
  - /auth/wecom/event # 企微指令回调:验签+解密即认证,无租户语义
200
+ - /publish/credential/exchange # 凭证兑换:CLI 的裸 fetch 不带租户头;表本身是共享表(B 组)
199
201
  # tenant.require_signed_claim:租户头的服务端绑定(平台级 fail-closed)。
200
202
  # 暂保持 false——上游 oj 的 jwt.sign 目前会静默丢弃自定义 claim(需求 R6),
201
203
  # 开启前无法把 tenant 放进 token。R6 落地后改 true,裸头即失去自证能力。
@@ -40,11 +40,11 @@ include:
40
40
  - src/styles.css
41
41
  # 后端 typecheck 工程(package.json 的 typecheck:api 指向它);只收后端、排除 web
42
42
  - src/tsconfig.json
43
- # —— 后端:配置 / 种子 / 测试 / 默认模块(模块源码在 src/app/api/<模块>)——
43
+ # —— 后端:配置 / 测试 / 默认模块(模块源码在 src/app/api/<模块>)——
44
+ # 方言 seed 不在这里列:它们是模块资产(`src/app/api/<模块>/seed.<dialect>.sql`),
45
+ # 根级副本会与模块内那份漂移(历史上一份只含 4 条设置、且 mysql 侧裸写保留字列),已删除。
44
46
  - config.yaml
45
47
  - .ojm-api-exempt.json
46
- - seed.mysql.sql
47
- - seed.pg.sql
48
48
  - vitest.config.ts
49
49
  # —— tests 逐条白名单(不收 tests/ 整目录)——
50
50
  # 业务工程只拷「纯 node:sqlite 夹具、零 cli 包环境假设」的测试。以下 6 个不分发
@@ -74,6 +74,9 @@ include:
74
74
  # 角色页展示层过滤 superadmin(后端 listRoles 仍返回;页面源码随 src/app/web/system 下发)
75
75
  - tests/roles-page.spec.ts
76
76
  - tests/seed-role-accounts.spec.ts
77
+ # 发布凭证:身份推导规则是跨端共用的口径,随源码下发,用户工程可本地自检
78
+ - tests/publish-identity.spec.ts
79
+ - tests/publish-credential.spec.ts
77
80
  # 用户页行级门禁:只有 superadmin 能改 superadmin 账号(后端 assertCanManageTarget 的另一面)
78
81
  - tests/users-page.spec.ts
79
82
  # 企微连接器与组织同步:源码本就分发(src/app/api/auth),对应回归也必须跟着走,
@@ -105,6 +108,7 @@ include:
105
108
  - src/app/api/auth
106
109
  - src/app/api/notification
107
110
  - src/app/api/personal-center
111
+ - src/app/api/publish
108
112
  - src/app/api/system
109
113
  - src/app/api/web
110
114
  # —— web 前端模块 ——
@@ -132,5 +136,6 @@ include:
132
136
  - src/oj-market/web/hooks
133
137
  - src/oj-market/web/locales
134
138
  - src/oj-market/web/pages
139
+ - src/oj-market/web/publish
135
140
  - src/oj-market/web/utils
136
141
  - src/app/web/web