@oj-module/cli 0.2.47 → 0.2.49

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 (101) hide show
  1. package/devkit/README.md +1 -1
  2. package/devkit/SKILL.md +23 -4
  3. package/devkit/framework-dev.md +2 -2
  4. package/devkit/manual.md +221 -14
  5. package/devkit/module-dev-and-cdn-playground.md +2 -2
  6. package/devkit/wecom-daikaifa.md +242 -14
  7. package/package.json +2 -2
  8. package/shell-dist/assets/runtime.js +2 -2
  9. package/shell-dist/index.html +2 -2
  10. package/shell-dist/versions.json +3 -3
  11. package/src/__tests__/build-backend.test.ts +80 -0
  12. package/src/__tests__/templates-tenant-scope.test.ts +7 -2
  13. package/src/build.ts +50 -0
  14. package/src/init.ts +1 -1
  15. package/src/market/__tests__/bootstrap.test.ts +1 -1
  16. package/src/market/__tests__/setup.test.ts +7 -7
  17. package/src/market/bootstrap.ts +1 -1
  18. package/templates/README.md +1 -1
  19. package/templates/init.include.yaml +12 -0
  20. package/templates/openapi.json +2863 -549
  21. package/templates/src/_platform/api/_shared/service/domain/contacts.ts +7 -0
  22. package/templates/src/_platform/api/_shared/service/domain/org-read.ts +451 -0
  23. package/templates/src/_platform/api/_shared/service/domain/org.ts +566 -0
  24. package/templates/src/_platform/api/_shared/service/domain/users.ts +140 -2
  25. package/templates/src/_platform/api/_shared/service/infra/query.ts +2 -1
  26. package/templates/src/_platform/api/fixtures/l1-test-user.sql +1 -1
  27. package/templates/src/_platform/api/manifest.yaml +3 -0
  28. package/templates/src/_platform/api/migrations/0016__org.mysql.sql +41 -0
  29. package/templates/src/_platform/api/migrations/0016__org.pg.sql +40 -0
  30. package/templates/src/_platform/api/migrations/0016__org.sql +62 -0
  31. package/templates/src/_platform/api/org/departments/api.ts +51 -0
  32. package/templates/src/_platform/api/org/is-member/api.ts +33 -0
  33. package/templates/src/_platform/api/org/members/api.ts +55 -0
  34. package/templates/src/_platform/api/org/picker/api.ts +59 -0
  35. package/templates/src/_platform/api/org/resolve/api.ts +38 -0
  36. package/templates/src/_platform/api/org/tree/api.ts +37 -0
  37. package/templates/src/_platform/api/org/user/api.ts +46 -0
  38. package/templates/src/_platform/api/schema.yaml +48 -0
  39. package/templates/src/_platform/api/seed.mysql.sql +10 -5
  40. package/templates/src/_platform/api/seed.pg.sql +10 -5
  41. package/templates/src/_platform/api/seed.sql +13 -5
  42. package/templates/src/_platform/web/__client/api.schemas.ts +126 -0
  43. package/templates/src/_platform/web/__client/api.ts +170 -1
  44. package/templates/src/auth/api/_shared/ldap-login.ts +2 -0
  45. package/templates/src/auth/api/_shared/session.ts +5 -1
  46. package/templates/src/auth/api/_shared/wecom/admin.ts +3 -1
  47. package/templates/src/auth/api/_shared/wecom/client.ts +280 -32
  48. package/templates/src/auth/api/_shared/wecom/config.ts +12 -1
  49. package/templates/src/auth/api/_shared/wecom/contacts.ts +263 -0
  50. package/templates/src/auth/api/_shared/wecom/crypt.ts +17 -7
  51. package/templates/src/auth/api/_shared/wecom/events.ts +111 -26
  52. package/templates/src/auth/api/_shared/wecom/login.ts +188 -5
  53. package/templates/src/auth/api/_shared/wecom/payload.ts +116 -0
  54. package/templates/src/auth/api/_shared/wecom/replay.ts +44 -3
  55. package/templates/src/auth/api/_shared/wecom/store.ts +65 -0
  56. package/templates/src/auth/api/_shared/wecom/sync.ts +501 -0
  57. package/templates/src/auth/api/manifest.yaml +3 -0
  58. package/templates/src/auth/api/migrations/0002__org_sync.mysql.sql +28 -0
  59. package/templates/src/auth/api/migrations/0002__org_sync.pg.sql +27 -0
  60. package/templates/src/auth/api/migrations/0002__org_sync.sql +41 -0
  61. package/templates/src/auth/api/schema.yaml +31 -0
  62. package/templates/src/auth/api/wecom/admin/api.ts +3 -1
  63. package/templates/src/auth/api/wecom/api.ts +155 -35
  64. package/templates/src/auth/api/wecom/sync/api.ts +199 -0
  65. package/templates/src/auth/web/__client/api.schemas.ts +97 -0
  66. package/templates/src/auth/web/__client/api.ts +73 -1
  67. package/templates/src/login/web/locales/en-US.json +2 -0
  68. package/templates/src/login/web/locales/zh-CN.json +2 -0
  69. package/templates/src/login/web/pages/login.tsx +39 -3
  70. package/templates/src/notification/api/seed.sql +1 -1
  71. package/templates/src/system/api/departments/api.ts +142 -0
  72. package/templates/src/system/api/departments/detach/api.ts +34 -0
  73. package/templates/src/system/api/departments/members/api.ts +142 -0
  74. package/templates/src/system/api/departments/move/api.ts +39 -0
  75. package/templates/src/system/api/users/api.ts +45 -5
  76. package/templates/src/system/api/users/profile/api.ts +44 -0
  77. package/templates/src/system/web/__client/api.schemas.ts +143 -0
  78. package/templates/src/system/web/__client/api.ts +293 -1
  79. package/templates/src/system/web/entry.ts +6 -0
  80. package/templates/src/system/web/locales/en-US.json +112 -6
  81. package/templates/src/system/web/locales/zh-CN.json +112 -6
  82. package/templates/src/system/web/pages/departments/index.tsx +346 -0
  83. package/templates/src/system/web/pages/departments/sync-bar.tsx +257 -0
  84. package/templates/src/system/web/pages/departments/tree.ts +66 -0
  85. package/templates/src/system/web/pages/errors.ts +1 -1
  86. package/templates/src/system/web/pages/users/index.tsx +49 -7
  87. package/templates/src/system/web/pages/wecom/index.tsx +14 -0
  88. package/templates/tests/l1-builder.test.ts +1 -1
  89. package/templates/tests/org-departments.spec.ts +311 -0
  90. package/templates/tests/org-read.spec.ts +469 -0
  91. package/templates/tests/rbac-contacts.spec.ts +13 -3
  92. package/templates/tests/rbac-seed.spec.ts +11 -0
  93. package/templates/tests/seed-role-accounts.spec.ts +6 -2
  94. package/templates/tests/wecom-admin.spec.ts +1 -0
  95. package/templates/tests/wecom-connector.spec.ts +146 -10
  96. package/templates/tests/wecom-crypt.spec.ts +12 -3
  97. package/templates/tests/wecom-endpoints.spec.ts +247 -19
  98. package/templates/tests/wecom-org-sync.spec.ts +598 -0
  99. package/templates/tests/wecom-sync.spec.ts +286 -0
  100. package/vendor/host-versions.json +3 -3
  101. package/templates/src/auth/api/_shared/wecom/xml.ts +0 -36
package/src/build.ts CHANGED
@@ -584,6 +584,9 @@ export async function buildBackend(projectRoot: string): Promise<boolean> {
584
584
  // v0.1.85:`--module-root <dir>` 与 `-d` 互斥(都是「源码树根」)。新布局给 src,
585
585
  // oj 在子树内递归找 manifest.yaml(命中 src/<name>/api/manifest.yaml 即停)。
586
586
  const sourceArgs = layout?.moduleRoot ? ["--module-root", layout.moduleRoot] : ["-d", apiSrc];
587
+ // 出包前先自己确认 config 能读:读不动时 oj build 会静默关掉 sql_guard(见函数注释)
588
+ if (configArgs.length)
589
+ assertSealedValuesOpenable(oj, configPath);
587
590
  execFileSync(oj, [
588
591
  "build",
589
592
  ...configArgs,
@@ -604,6 +607,53 @@ export async function buildBackend(projectRoot: string): Promise<boolean> {
604
607
  return true;
605
608
  }
606
609
 
610
+ /**
611
+ * config 里的密封值(`oj secret seal` 产物)在 build 前试解一把——fail-closed。
612
+ *
613
+ * **为什么不能只信 oj 的 warn**:实测(oj 0.1.115)config 读不动时 `oj build` **退出码仍是 0**,
614
+ * 只往 stderr 甩两行「读配置失败 → 镜像目录回落默认 tasks / 本次 build 按 sql_guard=off 处理」,
615
+ * 然后照常出包。那两行夹在模块清单里极易被忽略,而关掉的 `sql_guard` 正是 tenant 声明校验
616
+ * (跨模块 SQL 的唯一守卫)——包看起来完好,检查其实没跑。
617
+ *
618
+ * 探法就是 oj 自己的排障命令 `oj secret open`:它和 `oj serve` / `oj build` 用同一套密钥解析
619
+ * (`OJ_SECRET_KEY` → `OJ_SECRET_KEY_FILE` → config 的 `secrets.private_key_path`),
620
+ * 解不开就非零退出。明文只可能出现在它的 stdout,这里直接丢弃,不落终端也不落日志。
621
+ */
622
+ function assertSealedValuesOpenable(oj: string, configPath: string): void {
623
+ // 注释行不算:config 里留着注释掉的旧密文很常见,naive 匹配会让 build 因为一行 `#` 而失败
624
+ const live = fs.readFileSync(configPath, "utf-8")
625
+ .split("\n")
626
+ .filter(line => !line.trimStart().startsWith("#"))
627
+ .join("\n");
628
+ const sealed = [...new Set(live.match(/ENC\[[^\]\n]+\]/g) ?? [])];
629
+ for (const value of sealed) {
630
+ try {
631
+ execFileSync(oj, ["secret", "open", "-c", configPath, value], { stdio: ["ignore", "ignore", "pipe"] });
632
+ }
633
+ catch (e) {
634
+ const stderr = String((e as { stderr?: unknown }).stderr ?? "").trim();
635
+ const detail = stderr || (e as Error).message;
636
+ throw new Error(
637
+ `[ojm] config 里的密封值解不开,已停在出包前:${configPath}\n`
638
+ + ` oj 说:${detail}\n`
639
+ + "\n"
640
+ + "不能放行:oj build 遇到读不动的 config 不会失败(退出码 0),只 warn 两行就继续——\n"
641
+ + "tasks 镜像目录回落默认值,sql_guard(tenant 声明校验)直接关掉。那种包看着正常,\n"
642
+ + "跨模块 SQL 的守卫却没跑过。\n"
643
+ + "\n"
644
+ + "两类原因,都修得了:\n"
645
+ + " 1) 本机加载的私钥与密封时用的公钥不是一对——最常见是换机器/换部署目录时只带了\n"
646
+ + " config.yaml,没带 config/secrets-private.pem;\n"
647
+ + " 2) 密文被改过——手工编辑、复制被截断、编辑器把长行折了。\n"
648
+ + "\n"
649
+ + "单独验一条:oj secret open -c <config> '<那个 ENC[...] 值>'(成功=这条没问题,失败=就是它)\n"
650
+ + "修法:把配对私钥带到本机(OJ_SECRET_KEY / OJ_SECRET_KEY_FILE / config 的 secrets.private_key_path\n"
651
+ + " 三选一);密钥确实换了,就在本机重新 oj secret seal 覆写该值(明文走 stdin,别上命令行)。",
652
+ );
653
+ }
654
+ }
655
+ }
656
+
607
657
  /**
608
658
  * 全站合并(仅 `ojm build` 调用,设计 §5):清场后拷 shell dist 全量,
609
659
  * 再由模块构建写入 modules.json 与 modules/。清场防旧哈希资产无限累积
package/src/init.ts CHANGED
@@ -245,7 +245,7 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
245
245
  console.log(`[ojm] · 跳过 ${item}`);
246
246
  for (const item of report.warnings)
247
247
  console.warn(`[ojm] ! ${item}`);
248
- console.log("[ojm] 下一步:pnpm install && pnpm dev(登录 superadmin / 123456,角色账号见 devkit 手册 §1.1)");
248
+ console.log("[ojm] 下一步:pnpm install && pnpm dev(登录 superadmin / aA123456,角色账号见 devkit 手册 §1.1)");
249
249
  }
250
250
 
251
251
  /** 默认密封器:bin/oj secret keygen + seal(失败返回 null,不抛) */
@@ -120,7 +120,7 @@ describe("given 一个装了 oj 二进制的源工程 When 自举隔离站点",
120
120
  const site = await bootstrapIsolatedSite({ projectRoot: src, starter });
121
121
 
122
122
  expect(site.admin.user).toBe("superadmin");
123
- expect(site.admin.pass).toBe("123456");
123
+ expect(site.admin.pass).toBe("aA123456");
124
124
  await site.cleanup();
125
125
  });
126
126
 
@@ -62,7 +62,7 @@ describe("given 站点可直连且凭据未悬挂 When setup", () => {
62
62
  const root = newProject();
63
63
  const site = fakeSite();
64
64
 
65
- const result = await setupMarketUrl({ projectRoot: root, user: "admin", pass: "123456", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn });
65
+ const result = await setupMarketUrl({ projectRoot: root, user: "admin", pass: "aA123456", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn });
66
66
 
67
67
  expect(result.cdnBaseUrl).toBe("http://127.0.0.1:9778/cdn");
68
68
  const put = site.calls.find(c => c.url.endsWith("/api/system/settings"))!;
@@ -77,12 +77,12 @@ describe("given seed admin 处于 must_change 悬挂期 When setup", () => {
77
77
  const root = newProject();
78
78
  const site = fakeSite({ restricted: true });
79
79
 
80
- const result = await setupMarketUrl({ projectRoot: root, user: "admin", pass: "123456", newPass: "newpass-123", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn });
80
+ const result = await setupMarketUrl({ projectRoot: root, user: "admin", pass: "aA123456", newPass: "newpass-123", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn });
81
81
 
82
82
  expect(result.changedPassword).toBe(true);
83
83
  const pwd = site.calls.find(c => c.url.endsWith("/api/auth/password"))!;
84
84
  expect(pwd.method).toBe("PUT");
85
- expect(pwd.body).toEqual({ old_password: "123456", new_password: "newpass-123" });
85
+ expect(pwd.body).toEqual({ old_password: "aA123456", new_password: "newpass-123" });
86
86
  const put = site.calls.find(c => c.url.endsWith("/api/system/settings"))!;
87
87
  expect(put.auth).toBe("Bearer token-full");
88
88
  });
@@ -96,7 +96,7 @@ describe("given 站点不可达 When setup", () => {
96
96
  };
97
97
  const fetchFn = failing as unknown as typeof fetch;
98
98
 
99
- await expect(setupMarketUrl({ projectRoot: root, user: "admin", pass: "123456", siteUrl: "http://127.0.0.1:9778", fetchFn }))
99
+ await expect(setupMarketUrl({ projectRoot: root, user: "admin", pass: "aA123456", siteUrl: "http://127.0.0.1:9778", fetchFn }))
100
100
  .rejects
101
101
  .toThrow(/ojm dev/);
102
102
  });
@@ -107,7 +107,7 @@ describe("given 改密后写设置仍被拒 When setup", () => {
107
107
  const root = newProject();
108
108
  const site = fakeSite({ restricted: true, rejectSettings: true });
109
109
 
110
- await expect(setupMarketUrl({ projectRoot: root, user: "admin", pass: "123456", newPass: "newpass-123", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn }))
110
+ await expect(setupMarketUrl({ projectRoot: root, user: "admin", pass: "aA123456", newPass: "newpass-123", siteUrl: "http://127.0.0.1:9778", fetchFn: site.fetchFn }))
111
111
  .rejects
112
112
  .toThrow(/must_change|权限|设置/);
113
113
  });
@@ -115,7 +115,7 @@ describe("given 改密后写设置仍被拒 When setup", () => {
115
115
 
116
116
  describe("given setup 返回 changedPassword When 选 setup 后的登录口令", () => {
117
117
  it("then 改密已发生用 newPass,未改密用原种子口令(must_change=0 是当前 seed 常态)", () => {
118
- expect(loginPasswordAfterSetup({ siteUrl: "", cdnBaseUrl: "", changedPassword: true }, "new-pass", "123456")).toBe("new-pass");
119
- expect(loginPasswordAfterSetup({ siteUrl: "", cdnBaseUrl: "", changedPassword: false }, "new-pass", "123456")).toBe("123456");
118
+ expect(loginPasswordAfterSetup({ siteUrl: "", cdnBaseUrl: "", changedPassword: true }, "new-pass", "aA123456")).toBe("new-pass");
119
+ expect(loginPasswordAfterSetup({ siteUrl: "", cdnBaseUrl: "", changedPassword: false }, "new-pass", "aA123456")).toBe("aA123456");
120
120
  });
121
121
  });
@@ -39,7 +39,7 @@ import { publishPackage } from "./publish";
39
39
  // 种子管理员 = superadmin:模板 seed 账号名=角色名(3d93063f 起 admin 被幂等解绑 superadmin
40
40
  // 角色),写 market 安全设置要求 actor 持 superadmin 角色——用 admin 会 403(2026-10-23 演练实踩)。
41
41
  const SEED_ADMIN_USER = "superadmin";
42
- const SEED_ADMIN_PASS = "123456";
42
+ const SEED_ADMIN_PASS = "aA123456";
43
43
 
44
44
  /** Ed25519 PKCS#8 DER 前缀(RFC 8410 固定头;与 gen-signed-fixtures.mjs 同一手法) */
45
45
  const ED25519_PKCS8_PREFIX = "302e020100300506032b657004220420";
@@ -11,7 +11,7 @@ pnpm build # 构建:oj build + 前端全站合并到 dist/web
11
11
  pnpm preview # 预览:oj migrate + server(release 产物)
12
12
  ```
13
13
 
14
- 登录账号:`admin` / `123456`(见 `api/src/_platform/seed.sql`)。
14
+ 登录账号:`admin` / `aA123456`(见 `api/src/_platform/seed.sql`)。
15
15
 
16
16
  ## 系统管理模块
17
17
 
@@ -59,6 +59,8 @@ include:
59
59
  - tests/auth-throttle.spec.ts
60
60
  - tests/guard.spec.ts
61
61
  - tests/l1-builder.test.ts
62
+ - tests/org-departments.spec.ts
63
+ - tests/org-read.spec.ts
62
64
  - tests/query-utils.spec.ts
63
65
  - tests/rbac-audit.spec.ts
64
66
  - tests/rbac-contacts.spec.ts
@@ -70,6 +72,16 @@ include:
70
72
  - tests/rbac-tables.spec.ts
71
73
  - tests/rbac-users.spec.ts
72
74
  - tests/seed-role-accounts.spec.ts
75
+ # 企微连接器与组织同步:源码本就分发(src/auth/api),对应回归也必须跟着走,
76
+ # 否则用户工程升级后改配置/换套件时没有任何本地自检手段。
77
+ - tests/wecom-admin.spec.ts
78
+ - tests/wecom-client-timeout.spec.ts
79
+ - tests/wecom-connector.spec.ts
80
+ - tests/wecom-crypt.spec.ts
81
+ - tests/wecom-endpoints.spec.ts
82
+ - tests/wecom-landing.spec.ts
83
+ - tests/wecom-org-sync.spec.ts
84
+ - tests/wecom-sync.spec.ts
73
85
  - tests/lifecycle/atomic-state.spec.ts
74
86
  - tests/lifecycle/deploy-smoke.spec.ts
75
87
  - tests/lifecycle/migrate.spec.ts