@oj-module/cli 0.2.2 → 0.2.6

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 +189 -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 +62 -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,9 @@
1
+ # 声明式表结构(形态对齐 apps/playground-oj/api/src/demo/schema.yaml)。
2
+ # 契约 C2-12:bundle manifest 的 api.tables 必须与本表 keys 一致(oj 冗余校验 fail-fast)。
3
+ tables:
4
+ note:
5
+ pk: id
6
+ columns:
7
+ id: {type: integer, autoincrement: true}
8
+ title: {type: text, null: false}
9
+ create_time: {type: integer, null: false}
@@ -0,0 +1,96 @@
1
+ # ── C2 契约合法夹具(fixture-valid)────────────────────────────────────
2
+ # 覆盖《202610171254-lifecycle-manifest-contract》全部字段的合法样例。
3
+ # 双端测试期望:Node 侧 validateContract 与 isolate 侧 preflight 均零违规。
4
+ # 规则编号(C2-xx)见契约 §2/§4。
5
+ name: demo-solution # C2-01 白名单 + 非保留名
6
+ version: 0.1.0 # C2-02 oj 相容版本语法
7
+ title: 生命周期样例 # C2-03
8
+ summary: manifest.yaml 契约全字段合法夹具 # C2-04(→ oj 模块 desc)
9
+ description: | # C2-05
10
+ 用于 P1 双端契约测试的合成方案:覆盖身份/兼容/prefix/匿名/RBAC/
11
+ 路由分类/设置分组/i18n/web 全部契约字段。
12
+ icon: /icons/demo-solution.svg # C2-06
13
+ author: oj-module # C2-07
14
+ license: MIT
15
+ homepage: https://example.com/demo-solution
16
+ keywords: [fixture, lifecycle]
17
+
18
+ changelog: # C2-08(R27 市场卡片版本对比)
19
+ - 0.1.0 首个契约夹具版本:全字段覆盖
20
+
21
+ peerRuntime: '>=0.0.0' # C2-09(P1 预检对宿主 versions.json 强制)
22
+ peerOj: '>=0.1.76' # C2-10(P1 仅声明不强制)
23
+ peerFramework: '>=0.2.4'
24
+ peerPlatform: '>=0.1.0' # C2-11(P2 起对宿主 API_VERSION 强制;本仓 API_VERSION=0.1.0,夹具声明须可被其满足)
25
+
26
+ api:
27
+ module: demo-solution # C2-12(存在时必须 === name)
28
+ prefix: /api/demo-solution # C2-13(默认形态显式写出)
29
+ tables: [note] # 与 schema.yaml 表集一致(oj 冗余校验同源)
30
+ deps: # 键 = 模块名(C2-01 语法或 _platform);值 = 范围算子语法
31
+ _platform: '>=0.1.0'
32
+ anonymous: # C2-26:首段 === 模块段;无登录语义端点
33
+ - demo-solution/ping
34
+
35
+ web:
36
+ route_prefix: /demo-solution # C2-14
37
+ entry: modules/demo-solution/0.1.0/entry.js # C2-27(版本段 === version,POSIX)
38
+ # 构建器对 entry 产物计算写入;安装器 deployWeb→smoke 段重算比对(C2-28/R10)。
39
+ # 源树夹具中为格式样例值,产物级比对不在本夹具断言范围(见 README)。
40
+ integrity: sha384-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
41
+ css:
42
+ - modules/demo-solution/0.1.0/demo-solution.css
43
+
44
+ archive: # C2-29 Form A(tgz 内占位:仅 file;sha256/size/signature 由 P3 publish 填充于 Form B)
45
+ file: demo-solution-0.1.0.tgz
46
+
47
+ permissions: # C2-15:code 强制 <name>. 前缀
48
+ - {code: demo-solution.view, title_key: demo-solution:perm.view}
49
+ - {code: demo-solution.manage, title_key: demo-solution:perm.manage}
50
+
51
+ roles: # C2-16:name=<name>.<role>;permissions ⊆ permissions[].code
52
+ - name: demo-solution.admin
53
+ title_key: demo-solution:role.admin
54
+ permissions: [demo-solution.view, demo-solution.manage]
55
+ - name: demo-solution.viewer
56
+ title_key: demo-solution:role.viewer
57
+ permissions: [demo-solution.view]
58
+
59
+ routes: # C2-17~21:D4 三值全覆盖;order 建议 ≥200
60
+ - {path: /demo-solution, category: main, order: 200, title_key: demo-solution:menu.main}
61
+ - path: /admin/demo-solution
62
+ category: admin
63
+ order: 200
64
+ title_key: demo-solution:menu.admin
65
+ required_permission: demo-solution.manage # C2-21:∈ permissions[].code;R22 前端不下发
66
+
67
+ - {path: /demo-solution/report, category: hidden, order: 210, title_key: demo-solution:menu.report}
68
+
69
+ health: /demo-solution/ping # C2-33:冒烟探针(main 路由 /demo-solution 的子路径;匿名可达经 api.anonymous 声明,D14/Q-A)
70
+
71
+ settings:
72
+ groups: # C2-22~24(R24:moduleOf(key) === name)
73
+ - group: display
74
+ label_key: demo-solution:settings.group.display
75
+ items:
76
+ - key: demo-solution.display.theme
77
+ type: enum
78
+ default: auto
79
+ options: [auto, light, dark]
80
+ label_key: demo-solution:settings.display.theme
81
+ - key: demo-solution.display.page_size
82
+ type: int
83
+ default: '20'
84
+ label_key: demo-solution:settings.display.page_size
85
+ - group: notify
86
+ label_key: demo-solution:settings.group.notify
87
+ items:
88
+ - key: demo-solution.notify.enabled
89
+ type: bool
90
+ default: '0'
91
+ label_key: demo-solution:settings.notify.enabled
92
+ - key: demo-solution.notify.webhook
93
+ type: string
94
+ default: ''
95
+ secret: true
96
+ label_key: demo-solution:settings.notify.webhook
@@ -0,0 +1,23 @@
1
+ // 夹具 entry 占位:真实工程经 defineModule(@oj-module/runtime)声明,形态对齐
2
+ // apps/playground-oj/web/src/<module>/entry.ts。夹具仅承载结构与 i18n key 断言,
3
+ // 不参与构建与类型检查(不 import 外部模块)。
4
+ const entry = {
5
+ name: "demo-solution",
6
+ description: "manifest.yaml 契约全字段合法夹具",
7
+ version: "0.1.0",
8
+ peerRuntime: ">=0.0.0",
9
+ routes: [
10
+ {
11
+ path: "/demo-solution",
12
+ handle: { layout: "container", order: 200, title: "demo-solution:menu.main" },
13
+ children: [
14
+ {
15
+ path: "report",
16
+ handle: { title: "demo-solution:menu.report", hideInMenu: true },
17
+ },
18
+ ],
19
+ },
20
+ ],
21
+ };
22
+
23
+ export default entry;
@@ -0,0 +1,29 @@
1
+ {
2
+ "menu": {
3
+ "main": "Lifecycle Sample",
4
+ "admin": "Sample Admin",
5
+ "report": "Sample Report"
6
+ },
7
+ "perm": {
8
+ "view": "View sample",
9
+ "manage": "Manage sample"
10
+ },
11
+ "role": {
12
+ "admin": "Sample Admin Role",
13
+ "viewer": "Sample Viewer Role"
14
+ },
15
+ "settings": {
16
+ "group": {
17
+ "display": "Display Preferences",
18
+ "notify": "Notification Preferences"
19
+ },
20
+ "display": {
21
+ "theme": "Theme",
22
+ "page_size": "Page Size"
23
+ },
24
+ "notify": {
25
+ "enabled": "Enable Notifications",
26
+ "webhook": "Webhook URL"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "menu": {
3
+ "main": "生命周期样例",
4
+ "admin": "样例管理",
5
+ "report": "样例报告"
6
+ },
7
+ "perm": {
8
+ "view": "查看样例",
9
+ "manage": "管理样例"
10
+ },
11
+ "role": {
12
+ "admin": "样例管理员",
13
+ "viewer": "样例查看者"
14
+ },
15
+ "settings": {
16
+ "group": {
17
+ "display": "显示偏好",
18
+ "notify": "通知偏好"
19
+ },
20
+ "display": {
21
+ "theme": "主题",
22
+ "page_size": "每页条数"
23
+ },
24
+ "notify": {
25
+ "enabled": "启用通知",
26
+ "webhook": "Webhook 地址"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "key_id": "ojm.test",
4
+ "public_key": "6320cd295df4a2cf48b90282daae32e133fefd0459d71af92d6c376889d038ef",
5
+ "value": "AizOO9Xhj4iCJJj8GRORgl7G1IzvGHfEZ1tYq6NEuwxiVDdrX7Nu3sqhazxXyrfzybPa11M23RZtji4iLHq1Dw==",
6
+ "canon": "v1"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "key_id": "ojm.test",
4
+ "public_key": "6320cd295df4a2cf48b90282daae32e133fefd0459d71af92d6c376889d038ef",
5
+ "value": "ZizOO9Xhj4iCJJj8GRORgl7G1IzvGHfEZ1tYq6NEuwxiVDdrX7Nu3sqhazxXyrfzybPa11M23RZtji4iLHq1Dw==",
6
+ "canon": "v1"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "alg": "Ed25519",
3
+ "key_id": "stranger.unknown",
4
+ "public_key": "a329626d90b337a969f66b101ad8bda4dc95f8c6f000731083147c262e8bbabb",
5
+ "value": "A8X5XsXQJOvIY55I1Dnz7bB6Y/TY0yLJiGze9AFN+ZsHor5ZPlm09ZLgBRdaymw/cLaEgeSkLr6dnYXaKSDxDw==",
6
+ "canon": "v1"
7
+ }
@@ -0,0 +1,21 @@
1
+ /** gen-signed-fixtures.mjs 的类型声明(.mjs 无内联类型,供两端 TS 测试 import) */
2
+ export declare const FIXTURE_NAME: string;
3
+ export declare const FIXTURE_VERSION: string;
4
+ export declare const CANON: string;
5
+ export declare const TRUSTED_KEY: { key_id: string, public_key_hex: string };
6
+ export declare const UNKNOWN_KEY: { key_id: string, public_key_hex: string };
7
+ export declare function canonicalString(name: string, version: string, sha256: string): string;
8
+ export declare function fingerprintOf(publicKeyHex: string): string;
9
+ export declare function signCanonicalB64(privateKey: unknown, canonical: string): string;
10
+ export declare function verifyCanonicalB64(publicKeyHex: string, canonical: string, valueB64: string): boolean;
11
+ export declare function generate(outDir?: string): {
12
+ sha256: string
13
+ canonical: string
14
+ tgzPath: string
15
+ paths: { tgz: string, good: string, bad: string, unknown: string }
16
+ sig: {
17
+ good: { alg: string, key_id: string, public_key: string, value: string, canon: string }
18
+ bad: { alg: string, key_id: string, public_key: string, value: string, canon: string }
19
+ unknown: { alg: string, key_id: string, public_key: string, value: string, canon: string }
20
+ }
21
+ };
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ import { Buffer } from "node:buffer";
3
+ /**
4
+ * 签名链测试夹具生成器(P2-0,契约 v1.3 §2.11/§2.12 + 附录 A.2)。
5
+ *
6
+ * 为什么自包含(仅 node: 内建、不 import 仓库 TS):夹具要被两个 vitest 项目消费
7
+ * (packages/cli 与 packages/cli/templates/api 各自 root 锚定),.mjs 是两端都能直接
8
+ * import/spawn 的唯一形态;签名逻辑(规范串/sign)与 P2-1 的 sign.ts 同语义,
9
+ * 由 P2-1 spec「Node 对拍 + 夹具验签」钉住逐字一致(DRY 锚点在测试而非本文件重复实现)。
10
+ *
11
+ * 确定性(提交为产物后可重复再生):
12
+ * - 测试 key 由公开 seed 常量经 SHA-256 派生 32B → PKCS#8 DER 包裹 → Ed25519
13
+ * (seed 公开 = 测试 key 无保密性,私钥永不入仓——P2-12「模板/夹具不得含私钥」的前提);
14
+ * - ustar 手写(mtime=0、成员字典序、0644/0755、uid/gid=0、无 owner 名)+
15
+ * Node zlib gzip(头内 MTIME/FNAME 恒 0,同 deterministic-tar.ts 口径)。
16
+ *
17
+ * 产物(outDir,缺省 = 本文件所在目录):
18
+ * - demo-solution-0.2.0.tgz 共用包体(Form A,禁带 sha256/signature)
19
+ * - demo-solution-0.2.0.tz.sig 已签(TRUSTED_KEY)
20
+ * - demo-solution-0.2.0.tgz.bad.sig 篡改(value 首字符翻转,仍是合法标准 base64 → tampered 而非 sig_format)
21
+ * - demo-solution-0.2.0.tgz.unknown.sig 未知 key 签名(UNKNOWN_KEY,key_id 不在任何信任库)
22
+ */
23
+ import { createHash, createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
24
+ import fs from "node:fs";
25
+ import path from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+ import { gzipSync } from "node:zlib";
28
+
29
+ const FIXTURE_DIR = path.dirname(fileURLToPath(import.meta.url));
30
+
31
+ export const FIXTURE_NAME = "demo-solution";
32
+ export const FIXTURE_VERSION = "0.2.0";
33
+ /** 规范串版本标记(契约 §2.11:canon 字段恒 "v1") */
34
+ export const CANON = "v1";
35
+ const SEED_PREFIX = "ojm-lifecycle-fixture-v1:";
36
+
37
+ /** 契约 v1.3 §2.11 规范串格式(逐字,P2-1 照抄实现,禁止自造分隔符) */
38
+ export function canonicalString(name, version, sha256) {
39
+ return `${name}@${version}:${sha256}`;
40
+ }
41
+
42
+ /** 指纹 = sha256Hex(publicKey) 全 64 hex(契约 §2.11,UI 分组展示用) */
43
+ export function fingerprintOf(publicKeyHex) {
44
+ return createHash("sha256").update(Buffer.from(publicKeyHex, "hex")).digest("hex");
45
+ }
46
+
47
+ /** 标准 base64 编码的 Ed25519 签名(契约 §2.11:value 仅标准 base64,禁 base64url) */
48
+ export function signCanonicalB64(privateKey, canonical) {
49
+ return sign(null, Buffer.from(canonical, "utf8"), privateKey).toString("base64");
50
+ }
51
+
52
+ /** Node 侧验签(与 P2-1 verifyCanonical / P2-3 crypto.verifyEd25519 三方同语义) */
53
+ export function verifyCanonicalB64(publicKeyHex, canonical, valueB64) {
54
+ const publicKey = createPublicKey({
55
+ key: Buffer.concat([Buffer.from("302a300506032b6570032100", "hex"), Buffer.from(publicKeyHex, "hex")]),
56
+ format: "der",
57
+ type: "spki",
58
+ });
59
+ return verify(null, Buffer.from(canonical, "utf8"), publicKey, Buffer.from(valueB64, "base64"));
60
+ }
61
+
62
+ function deriveKeyPair(label) {
63
+ const seed = createHash("sha256").update(SEED_PREFIX + label).digest(); // 32B raw seed
64
+ const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
65
+ const privateKey = createPrivateKey({ key: der, format: "der", type: "pkcs8" });
66
+ const jwk = createPublicKey(privateKey).export({ format: "jwk" });
67
+ return { privateKey, publicKeyHex: Buffer.from(jwk.x, "base64url").toString("hex") };
68
+ }
69
+
70
+ const trusted = deriveKeyPair("trusted");
71
+ const unknown = deriveKeyPair("unknown");
72
+
73
+ /** 已信任测试 key(key_id 满足 §2.11 命名空间 <publisher>.<id>) */
74
+ export const TRUSTED_KEY = { key_id: "ojm.test", public_key_hex: trusted.publicKeyHex };
75
+ /** 未知发布方测试 key(签名合法但 key_id 不在信任库 → unknown_key/needs_trust 场景) */
76
+ export const UNKNOWN_KEY = { key_id: "stranger.unknown", public_key_hex: unknown.publicKeyHex };
77
+
78
+ // ── 确定性 ustar tgz(最小形态:目录成员在子成员之前、字典序)──────────────────
79
+
80
+ const BLOCK = 512;
81
+ const RECORD = 20 * BLOCK;
82
+
83
+ function octal(n, width) {
84
+ return `${n.toString(8).padStart(width - 1, "0")}\0`;
85
+ }
86
+
87
+ function header(relPath, size, typeflag) {
88
+ const h = Buffer.alloc(BLOCK);
89
+ h.write(relPath, 0, 100, "utf8");
90
+ h.write(typeflag === "5" ? "0000755\0" : "0000644\0", 100, 8, "utf8");
91
+ h.write("0000000\0", 108, 8, "utf8"); // uid
92
+ h.write("0000000\0", 116, 8, "utf8"); // gid
93
+ h.write(octal(size, 12), 124, 12, "utf8");
94
+ h.write("00000000000\0", 136, 12, "utf8"); // mtime=0
95
+ h.write(" ", 148, 8, "utf8"); // chksum 占位(空格)
96
+ h.write(typeflag, 156, 1, "utf8");
97
+ h.write("ustar\0", 257, 6, "utf8");
98
+ h.write("00", 263, 2, "utf8");
99
+ let sum = 0;
100
+ for (const b of h) sum += b;
101
+ h.write(`${sum.toString(8).padStart(6, "0")}\0 `, 148, 8, "utf8");
102
+ return h;
103
+ }
104
+
105
+ function buildTgz(members) {
106
+ const body = [];
107
+ for (const m of members) {
108
+ body.push(header(m.path, m.content ? m.content.length : 0, m.content ? "0" : "5"));
109
+ if (m.content) {
110
+ body.push(m.content);
111
+ const pad = (BLOCK - (m.content.length % BLOCK)) % BLOCK;
112
+ if (pad > 0)
113
+ body.push(Buffer.alloc(pad));
114
+ }
115
+ }
116
+ body.push(Buffer.alloc(RECORD)); // 双零块 EOF + 补齐整记录
117
+ return gzipSync(Buffer.concat(body));
118
+ }
119
+
120
+ const BUNDLE_MANIFEST = `# 签名链测试夹具(P2-0,契约 v1.3)——Form A:禁带 sha256/size/signature(B-2 自引用悖论)
121
+ name: ${FIXTURE_NAME}
122
+ version: ${FIXTURE_VERSION}
123
+ title: 签名链夹具
124
+ summary: detached .sig 验签/TOFU/吊销场景共用包体
125
+ changelog:
126
+ - ${FIXTURE_VERSION} 签名链夹具版本
127
+ `;
128
+
129
+ const MODULE_MANIFEST = `name: ${FIXTURE_NAME}
130
+ desc: detached .sig 验签/TOFU/吊销场景共用包体
131
+ version: ${FIXTURE_VERSION}
132
+ `;
133
+
134
+ function fixtureMembers() {
135
+ const root = `${FIXTURE_NAME}-${FIXTURE_VERSION}`;
136
+ const members = [
137
+ { path: `${root}/` },
138
+ { path: `${root}/manifest.yaml`, content: Buffer.from(BUNDLE_MANIFEST, "utf8") },
139
+ { path: `${root}/api/` },
140
+ { path: `${root}/api/${root}/` },
141
+ { path: `${root}/api/${root}/manifest.yaml`, content: Buffer.from(MODULE_MANIFEST, "utf8") },
142
+ ];
143
+ members.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
144
+ return members;
145
+ }
146
+
147
+ function tamperFirstChar(valueB64) {
148
+ return (valueB64[0] === "A" ? "B" : "A") + valueB64.slice(1);
149
+ }
150
+
151
+ /**
152
+ * 生成全部夹具到 outDir,返回摘要(sha256/canonical/各 .sig 路径)。
153
+ * 幂等且确定:同版本生成器对同 outDir 重复运行字节一致。
154
+ */
155
+ export function generate(outDir = FIXTURE_DIR) {
156
+ const tgz = buildTgz(fixtureMembers());
157
+ const sha256 = createHash("sha256").update(tgz).digest("hex");
158
+ const canonical = canonicalString(FIXTURE_NAME, FIXTURE_VERSION, sha256);
159
+
160
+ const sigOf = (keyId, publicKeyHex, privateKey) => ({
161
+ alg: "Ed25519",
162
+ key_id: keyId,
163
+ public_key: publicKeyHex,
164
+ value: signCanonicalB64(privateKey, canonical),
165
+ canon: CANON,
166
+ });
167
+
168
+ const good = sigOf(TRUSTED_KEY.key_id, TRUSTED_KEY.public_key_hex, trusted.privateKey);
169
+ const bad = { ...good, value: tamperFirstChar(good.value) };
170
+ const unknownSig = sigOf(UNKNOWN_KEY.key_id, UNKNOWN_KEY.public_key_hex, unknown.privateKey);
171
+
172
+ fs.mkdirSync(outDir, { recursive: true });
173
+ const tgzPath = path.join(outDir, `${FIXTURE_NAME}-${FIXTURE_VERSION}.tgz`);
174
+ const writeSig = (suffix, obj) => {
175
+ const p = `${tgzPath}${suffix}`;
176
+ fs.writeFileSync(p, `${JSON.stringify(obj, null, 2)}\n`);
177
+ return p;
178
+ };
179
+ fs.writeFileSync(tgzPath, tgz);
180
+ const paths = {
181
+ tgz: tgzPath,
182
+ good: writeSig(".sig", good),
183
+ bad: writeSig(".bad.sig", bad),
184
+ unknown: writeSig(".unknown.sig", unknownSig),
185
+ };
186
+ return { sha256, canonical, tgzPath, paths, sig: { good, bad, unknown: unknownSig } };
187
+ }
188
+
189
+ // 直接运行 = 再生成本目录产物(CI/人工复核用)
190
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
191
+ const r = generate();
192
+ console.log(`[gen-signed-fixtures] ${FIXTURE_NAME}-${FIXTURE_VERSION}.tgz sha256=${r.sha256}`);
193
+ console.log(`[gen-signed-fixtures] canonical=${r.canonical}`);
194
+ console.log(`[gen-signed-fixtures] trusted=${TRUSTED_KEY.key_id} fp=${fingerprintOf(TRUSTED_KEY.public_key_hex)}`);
195
+ console.log(`[gen-signed-fixtures] unknown=${UNKNOWN_KEY.key_id} fp=${fingerprintOf(UNKNOWN_KEY.public_key_hex)}`);
196
+ }
@@ -0,0 +1,78 @@
1
+ import type { KeyPair } from "../keygen";
2
+ // 签名链测试 helper(P2-1):测试运行时算摘要与签名,替代硬编码常量。
3
+ // 消费方:P2-3 起全部安装路径用例(G4 下未签名夹具必被打红,P2-16 全量改造的替换件)。
4
+ import { Buffer } from "node:buffer";
5
+ import { createHash, createPublicKey, verify as nodeVerify } from "node:crypto";
6
+ import { generateKeyPair } from "../keygen";
7
+ import { CANON, canonicalString, fingerprintOfPublicKey, SIG_ALG, signCanonical } from "../sign";
8
+
9
+ /** Ed25519 SPKI DER 前缀(12B)+ 32B raw 公钥 */
10
+ const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
11
+
12
+ /**
13
+ * Node 侧验签面(上游 crypto.verifyEd25519 的测试替身;同签名同语义:
14
+ * hex 32B 公钥 + UTF-8 msg + 标准 base64 签名;形状非法返回 false 而非抛错——
15
+ * 抛错/返 false 的分码由 verify 段自己负责,替身只当「原语在场」用)。
16
+ */
17
+ export function nodeVerifyEd25519(publicKeyHex: string, msg: string, sigB64: string): boolean {
18
+ if (!/^[0-9a-f]{64}$/.test(publicKeyHex) || !/^[A-Z0-9+/]+={0,2}$/i.test(sigB64))
19
+ return false;
20
+ try {
21
+ const key = createPublicKey({ key: Buffer.concat([SPKI_PREFIX, Buffer.from(publicKeyHex, "hex")]), format: "der", type: "spki" });
22
+ return nodeVerify(null, Buffer.from(msg, "utf8"), key, Buffer.from(sigB64, "base64"));
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /** detached 侧车 / Form B archive.signature 的字段形状(契约 §2.11 / C2-35) */
30
+ export interface SigFile {
31
+ alg: string
32
+ key_id: string
33
+ public_key: string
34
+ value: string
35
+ canon: string
36
+ }
37
+
38
+ export interface SignedArchive {
39
+ /** 与包体匹配的 .sig 对象(上传/写盘即此 JSON) */
40
+ sig: SigFile
41
+ keyId: string
42
+ publicKeyHex: string
43
+ fingerprint: string
44
+ /** 生成用的密钥对(测试自行加入信任库或用于未知 key 场景) */
45
+ keyPair: KeyPair
46
+ }
47
+
48
+ let defaultKey: KeyPair | null = null;
49
+
50
+ /** 进程内稳定的默认测试 key(每测试文件首次用时生成;跨文件不共享——无共享秘密) */
51
+ export function defaultTestKey(): KeyPair {
52
+ defaultKey ??= generateKeyPair();
53
+ return defaultKey;
54
+ }
55
+
56
+ /**
57
+ * 对 tgz 字节签名:sha256(bytes) → 规范串 → Ed25519 → .sig 五字段。
58
+ * `keyPair` 缺省 = defaultTestKey();传第二个 keyPair 即「未知发布方」场景。
59
+ */
60
+ export function signedArchiveFor(tgz: Uint8Array, name: string, version: string, opts?: { keyPair?: KeyPair, keyId?: string }): SignedArchive {
61
+ const keyPair = opts?.keyPair ?? defaultTestKey();
62
+ const sha256 = createHash("sha256").update(tgz).digest("hex");
63
+ const value = signCanonical(keyPair.privateKeyPem, canonicalString(name, version, sha256));
64
+ return {
65
+ sig: { alg: SIG_ALG, key_id: opts?.keyId ?? "test.publisher", public_key: keyPair.publicKeyHex, value, canon: CANON },
66
+ keyId: opts?.keyId ?? "test.publisher",
67
+ publicKeyHex: keyPair.publicKeyHex,
68
+ fingerprint: fingerprintOfPublicKey(keyPair.publicKeyHex),
69
+ keyPair,
70
+ };
71
+ }
72
+
73
+ /** 便捷重载:直接给 Buffer(node fs 读出的常见形态) */
74
+ export function signedArchiveForBuffer(tgz: Buffer, name: string, version: string, opts?: { keyPair?: KeyPair, keyId?: string }): SignedArchive {
75
+ return signedArchiveFor(new Uint8Array(tgz), name, version, opts);
76
+ }
77
+
78
+ export { Buffer };