@oj-module/cli 0.2.49 → 0.2.53

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 (75) hide show
  1. package/devkit/README.md +8 -6
  2. package/devkit/SKILL.md +32 -12
  3. package/devkit/manual.md +6 -3
  4. package/devkit/wecom-daikaifa.md +95 -1
  5. package/devkit/wecom-internal-app.md +750 -0
  6. package/package.json +2 -2
  7. package/shell-dist/assets/{index-FYt9CjFd.js → index-e_Mp2mb5.js} +5 -1
  8. package/shell-dist/assets/runtime.js +24 -3
  9. package/shell-dist/index.html +9 -12
  10. package/shell-dist/versions.json +3 -3
  11. package/src/build.ts +19 -2
  12. package/src/init.ts +5 -4
  13. package/src/shared-deps.ts +27 -9
  14. package/src/site-base.ts +1 -1
  15. package/templates/config.yaml +28 -6
  16. package/templates/init.include.yaml +4 -7
  17. package/templates/openapi.json +2636 -2356
  18. package/templates/src/_platform/api/_shared/service/domain/users.ts +40 -4
  19. package/templates/src/auth/api/_shared/wecom/admin.ts +91 -7
  20. package/templates/src/auth/api/_shared/wecom/client.ts +9 -2
  21. package/templates/src/auth/api/_shared/wecom/config.ts +256 -46
  22. package/templates/src/auth/api/_shared/wecom/contacts.ts +7 -1
  23. package/templates/src/auth/api/_shared/wecom/events.ts +28 -6
  24. package/templates/src/auth/api/_shared/wecom/login.ts +21 -13
  25. package/templates/src/auth/api/_shared/wecom/store.ts +61 -12
  26. package/templates/src/auth/api/_shared/wecom/sync.ts +11 -1
  27. package/templates/src/auth/api/_shared/wecom/tenant-admin.ts +194 -0
  28. package/templates/src/auth/api/migrations/0003__credential_kind.mysql.sql +3 -0
  29. package/templates/src/auth/api/migrations/0003__credential_kind.pg.sql +3 -0
  30. package/templates/src/auth/api/migrations/0003__credential_kind.sql +9 -0
  31. package/templates/src/auth/api/schema.yaml +2 -0
  32. package/templates/src/auth/api/wecom/admin/api.ts +112 -11
  33. package/templates/src/auth/api/wecom/admin/tenant-admin/api.ts +41 -0
  34. package/templates/src/auth/api/wecom/admin/tenant-admin/password/api.ts +66 -0
  35. package/templates/src/auth/api/wecom/api.ts +102 -48
  36. package/templates/src/auth/api/wecom/sync/api.ts +21 -5
  37. package/templates/src/auth/web/__client/api.schemas.ts +22 -1
  38. package/templates/src/auth/web/__client/api.ts +47 -1
  39. package/templates/src/login/web/pages/login.tsx +54 -24
  40. package/templates/src/system/api/settings/api.ts +3 -3
  41. package/templates/src/system/web/entry.ts +4 -4
  42. package/templates/src/system/web/locales/en-US.json +38 -1
  43. package/templates/src/system/web/locales/zh-CN.json +38 -1
  44. package/templates/src/system/web/pages/departments/sync-bar.tsx +32 -7
  45. package/templates/src/system/web/pages/users/index.tsx +7 -4
  46. package/templates/src/system/web/pages/users/roles.tsx +14 -3
  47. package/templates/src/system/web/pages/wecom/index.tsx +292 -45
  48. package/templates/tests/wecom-admin.spec.ts +7 -7
  49. package/templates/tests/wecom-connector.spec.ts +7 -6
  50. package/templates/tests/wecom-corp.spec.ts +523 -0
  51. package/templates/tests/wecom-endpoints.spec.ts +10 -4
  52. package/templates/tests/wecom-org-sync.spec.ts +7 -1
  53. package/templates/tsconfig.paths.json +0 -1
  54. package/templates/web.config.ts +1 -2
  55. package/vendor/host-versions.json +3 -3
  56. package/shell-dist/assets/echarts-charts.js +0 -58571
  57. package/shell-dist/assets/echarts-features.js +0 -24057
  58. package/shell-dist/assets/echarts-for-react.js +0 -704
  59. package/shell-dist/assets/echarts.js +0 -82916
  60. package/shell-dist/assets/editor.js +0 -161457
  61. package/shell-dist/assets/vditor-index-css.js +0 -7
  62. package/shell-dist/assets/vditor.js +0 -15223
  63. package/templates/src/demo/api/manifest.yaml +0 -5
  64. package/templates/src/demo/api/migrations/0001__create_todos.sql +0 -9
  65. package/templates/src/demo/api/migrations/0002__tenant.mysql.sql +0 -2
  66. package/templates/src/demo/api/migrations/0002__tenant.sql +0 -2
  67. package/templates/src/demo/api/schema.yaml +0 -12
  68. package/templates/src/demo/api/seed.sql +0 -5
  69. package/templates/src/demo/api/todos/api.ts +0 -59
  70. package/templates/src/demo/web/__client/api.schemas.ts +0 -20
  71. package/templates/src/demo/web/__client/api.ts +0 -89
  72. package/templates/src/demo/web/entry.ts +0 -42
  73. package/templates/src/demo/web/locales/en-US.json +0 -10
  74. package/templates/src/demo/web/locales/zh-CN.json +0 -10
  75. package/templates/src/demo/web/pages/index.tsx +0 -45
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oj-module/cli",
3
3
  "type": "module",
4
- "version": "0.2.49",
4
+ "version": "0.2.53",
5
5
  "description": "React Antd 模块工程命令行工具(含预构建宿主 shell-dist)",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -49,7 +49,7 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@faker-js/faker": "^10.4.0",
52
- "@oj-module/runtime": "0.2.49",
52
+ "@oj-module/runtime": "0.2.53",
53
53
  "@redocly/cli": "^2.51.0",
54
54
  "@tailwindcss/vite": "^4.3.0",
55
55
  "cac": "^6.7.14",
@@ -1,5 +1,5 @@
1
1
  import { StyleProvider } from "@ant-design/cssinjs";
2
- import { AntdApp, ErrorBoundary, LayoutEffects, PageError, getHostRoutes, getRoutes, loadAll, setupI18n, useAccessStore, useAuthStore, usePreferences, useUserStore } from "@oj-module/runtime";
2
+ import { AntdApp, ErrorBoundary, LayoutEffects, PageError, getHostRoutes, getRoutes, loadAll, resolveApiBase, setupI18n, useAccessStore, useAuthStore, usePreferences, useUserStore } from "@oj-module/runtime";
3
3
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4
4
  import { ConfigProvider, theme } from "antd";
5
5
  import { useEffect, useState } from "react";
@@ -312,6 +312,10 @@ function Boot() {
312
312
  let cancelled = false;
313
313
  (async () => {
314
314
  try {
315
+ if (typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent) && location.pathname.endsWith("/login") && !new URLSearchParams(location.search).get("wecom_ticket")) {
316
+ window.location.assign(resolveApiBase() + "/auth/wecom/login");
317
+ return;
318
+ }
315
319
  ensureI18n();
316
320
  const base = resolveSiteBase();
317
321
  const [res, versionsRes] = await Promise.all([fetch(`${base}modules.json`), fetch(`${base}versions.json`).catch(() => null)]);
@@ -304,10 +304,10 @@ function getAppInfo() {
304
304
  return {
305
305
  "pkg": {
306
306
  "name": "@oj-module/runtime",
307
- "version": "0.2.49",
307
+ "version": "0.2.53",
308
308
  "license": "MIT"
309
309
  },
310
- "lastBuildTime": "2026-11-01 17:14:09"
310
+ "lastBuildTime": "2026-11-02 15:46:13"
311
311
  };
312
312
  }
313
313
  var init_get_app_info = __esmMin((() => {}));
@@ -7687,6 +7687,27 @@ function buildInstance(entry, definition, status, error) {
7687
7687
  error
7688
7688
  };
7689
7689
  }
7690
+ /**
7691
+ * 按 `handle.roles` 递归过滤路由(菜单与路由表同源)。
7692
+ *
7693
+ * 模块级 `config.requiredRoles` 只能整包放行/拦截;一个模块里既有全员可见页、
7694
+ * 又有仅超管可见页(/system/settings、/system/wecom)时,需要路由级粒度。
7695
+ * 语义与前端静态路由(generateRoutesByFrontend)一致:未声明 = 不限制;
7696
+ * 声明了则命中任一角色才保留,否则连同子树一起剔除。
7697
+ */
7698
+ function filterRoutesByRoles(routes, roles) {
7699
+ const out = [];
7700
+ for (const route of routes) {
7701
+ const required = route.handle?.roles;
7702
+ if (required?.length && !required.some((role) => roles.includes(role))) continue;
7703
+ const children = "children" in route && Array.isArray(route.children) ? filterRoutesByRoles(route.children, roles) : void 0;
7704
+ out.push(children ? {
7705
+ ...route,
7706
+ children
7707
+ } : route);
7708
+ }
7709
+ return out;
7710
+ }
7690
7711
  function getRoutes() {
7691
7712
  const { roles, permissions = [] } = useUserStore.getState();
7692
7713
  const routes = [];
@@ -7696,7 +7717,7 @@ function getRoutes() {
7696
7717
  if (requiredRoles?.length && !requiredRoles.some((role) => roles.includes(role))) continue;
7697
7718
  const requiredPermissions = instance.definition.config?.requiredPermissions;
7698
7719
  if (requiredPermissions?.length && !requiredPermissions.every((perm) => permissions.includes(perm))) continue;
7699
- if (instance.definition.routes.length > 0) routes.push(...resolveRouteLayouts(tagModuleRoutes(instance.definition.routes, instance.definition.name)));
7720
+ if (instance.definition.routes.length > 0) routes.push(...filterRoutesByRoles(resolveRouteLayouts(tagModuleRoutes(instance.definition.routes, instance.definition.name)), roles));
7700
7721
  }
7701
7722
  return addRouteIdByPath(routes);
7702
7723
  }
@@ -5,9 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" href="/favicon.ico" />
7
7
  <title>React Antd Admin · Shell</title>
8
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-E48VJYAFCfSkkLa0nv6GVw=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
-
10
- <script type="importmap" nonce="E48VJYAFCfSkkLa0nv6GVw==">{
8
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-vlbkVLVjOGWbsP3B+GCfRg=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
9
+ <script type="importmap" nonce="vlbkVLVjOGWbsP3B+GCfRg==">{
11
10
  "imports": {
12
11
  "react": "/assets/react.js",
13
12
  "react/jsx-runtime": "/assets/jsx-runtime.js",
@@ -21,7 +20,6 @@
21
20
  "@oj-module/runtime/contract": "/assets/contract.js",
22
21
  "@oj-module/runtime/contract/errors": "/assets/contract-errors.js",
23
22
  "@plane/kit": "/assets/kit.js",
24
- "@plane/editor": "/assets/editor.js",
25
23
  "antd": "/assets/antd.js",
26
24
  "antd/locale/en_US": "/assets/antd-locale-en_US.js",
27
25
  "antd/locale/zh_CN": "/assets/antd-locale-zh_CN.js",
@@ -50,10 +48,6 @@
50
48
  "dayjs/plugin/weekday": "/assets/dayjs-plugin-weekday.js",
51
49
  "dayjs/plugin/weekOfYear": "/assets/dayjs-plugin-weekOfYear.js",
52
50
  "dayjs/plugin/weekYear": "/assets/dayjs-plugin-weekYear.js",
53
- "echarts": "/assets/echarts.js",
54
- "echarts/charts": "/assets/echarts-charts.js",
55
- "echarts/features.js": "/assets/echarts-features.js",
56
- "echarts-for-react": "/assets/echarts-for-react.js",
57
51
  "motion": "/assets/motion.js",
58
52
  "motion/react": "/assets/motion-react.js",
59
53
  "@dnd-kit/core": "/assets/dnd-kit-core.js",
@@ -73,8 +67,6 @@
73
67
  "simplebar-react": "/assets/simplebar-react.js",
74
68
  "spin-delay": "/assets/spin-delay.js",
75
69
  "tailwind-merge": "/assets/tailwind-merge.js",
76
- "vditor": "/assets/vditor.js",
77
- "vditor/dist/index.css": "/assets/vditor-index-css.js",
78
70
  "antd/es/modal": "/assets/ojm-antd-es-modal.js",
79
71
  "antd/es/upload": "/assets/ojm-antd-es-upload.js",
80
72
  "antd/es/button": "/assets/ojm-antd-es-button.js",
@@ -133,9 +125,14 @@
133
125
  "dayjs/locale/zh-cn": "/assets/ojm-dayjs-locale-zh-cn.js"
134
126
  }
135
127
  }</script>
136
- <script type="module" crossorigin src="/assets/index-FYt9CjFd.js"></script>
128
+ <style>
129
+ .boot-skeleton{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:#999;font-size:14px;background:#fff;z-index:9999}
130
+ .boot-skeleton:after{content:"";width:18px;height:18px;margin-left:8px;border:2px solid #d9d9d9;border-top-color:#1677ff;border-radius:50%;animation:bs-spin .8s linear infinite}
131
+ @keyframes bs-spin{to{transform:rotate(360deg)}}
132
+ </style>
133
+ <script nonce="vlbkVLVjOGWbsP3B+GCfRg==" data-ojm-entry="/assets/index-e_Mp2mb5.js">(function(){try{var ua=(navigator.userAgent||"").toLowerCase(),p=location.pathname;while(p.length>1&&p.charAt(p.length-1)==="/")p=p.slice(0,-1);if(ua.indexOf("wxwork")>=0&&p.slice(-6)==="/login"&&!new URLSearchParams(location.search).get("wecom_ticket")){var a=(typeof window.__OJ_API_BASE__==="string"&&window.__OJ_API_BASE__.trim())||"/api";while(a.length>1&&a.charAt(a.length-1)==="/")a=a.slice(0,-1);location.replace(a+"/auth/wecom/login");return}}catch(e){}var s=document.createElement("script");s.type="module";s.src=document.currentScript.getAttribute("data-ojm-entry");document.head.appendChild(s);})();</script>
137
134
  </head>
138
135
  <body>
139
- <div id="root"></div>
136
+ <div id="root"><div class="boot-skeleton">加载中…</div></div>
140
137
  </body>
141
138
  </html>
@@ -7,9 +7,9 @@
7
7
  "react-router": "7.18.2",
8
8
  "react-router/dom": "7.18.2",
9
9
  "@tanstack/react-query": "5.102.8",
10
- "@oj-module/runtime": "0.2.49",
11
- "@oj-module/runtime/contract": "0.2.49",
12
- "@oj-module/runtime/contract/errors": "0.2.49",
10
+ "@oj-module/runtime": "0.2.53",
11
+ "@oj-module/runtime/contract": "0.2.53",
12
+ "@oj-module/runtime/contract/errors": "0.2.53",
13
13
  "@plane/kit": "0.1.20",
14
14
  "@plane/editor": "0.1.20",
15
15
  "antd": "6.6.1",
package/src/build.ts CHANGED
@@ -16,7 +16,7 @@ import { ALIAS_PATHS_FILE, syncAliasPaths } from "./alias-paths";
16
16
  import { assertModuleList, loadSiteConfig, mergeModuleList, resolveModuleEntry } from "./config";
17
17
  import { moduleDir, resolveConfigPath, resolveLayout, scanModules, tryResolveLayout } from "./layout";
18
18
  import { requireModuleRootCapability } from "./oj-caps";
19
- import { isSharedDep, SHARED_DEPS } from "./shared-deps";
19
+ import { isPrebuiltDep, isSharedDep, SHARED_DEPS } from "./shared-deps";
20
20
  import { normalizeSiteBase, rewriteIndexHtml } from "./site-base";
21
21
  import { resolveOjBin } from "./vendor";
22
22
  import { checkSharedVersions, resolveShellDist } from "./versions";
@@ -790,7 +790,7 @@ export async function buildModules(
790
790
  rolldownOptions: {
791
791
  // 只把共享依赖留给宿主 importmap;
792
792
  // 相对路径 / 绝对路径是模块自身代码,需要打包
793
- external: (id: string) => !id.startsWith(".") && !path.isAbsolute(id) && isSharedDep(id),
793
+ external: (id: string) => !id.startsWith(".") && !path.isAbsolute(id) && isPrebuiltDep(id),
794
794
  output: {
795
795
  // P7.x / e2e:模块产物内联动态 import(lazy 页面、i18n 等),
796
796
  // 输出为**单文件**。原因:
@@ -807,6 +807,23 @@ export async function buildModules(
807
807
  },
808
808
  });
809
809
 
810
+ // 运行期 require 门禁:CJS 依赖 require 了 external 共享包时,rolldown 只留一个浏览器
811
+ // 不可用的 require 桩("Calling `require` for ..."),表现为模块加载即抛错、页面空白。
812
+ // 这类依赖必须移出 bundle 走宿主 importmap(shell 侧有 CJS→ESM 垫片)。
813
+ const builtEntry = path.join(moduleOutDir, "entry.js");
814
+ if (fs.existsSync(builtEntry)) {
815
+ const code = fs.readFileSync(builtEntry, "utf-8");
816
+ if (code.includes("doesn't expose the `require` function")) {
817
+ const specs = [...new Set([...code.matchAll(/ne\("([^"]+)"\)/g)].map(m => m[1]))];
818
+ throw new Error(
819
+ `[ojm] ${definition.name}@${definition.version} 产物含运行期 require(${specs.join(", ") || "?"})`
820
+ + "——浏览器没有 require,模块加载即抛错。"
821
+ + "根因:该 CJS 依赖被标为 bundle(自带副本),但它 require 的共享包被 external 了。"
822
+ + "修复:把它从 SHARED_DEPS 的 bundle 列表移回共享资产,或改用该依赖的 ESM 入口。",
823
+ );
824
+ }
825
+ }
826
+
810
827
  const prefix = `${baseUrl}/modules/${definition.name}/${definition.version}/`;
811
828
  const chunks: ChunkEntry[] = [];
812
829
  const css: string[] = [];
package/src/init.ts CHANGED
@@ -193,10 +193,11 @@ export async function initProject(destDir: string, opts: InitOptions = {}): Prom
193
193
  if (fs.existsSync(path.join(cliDevkit, "SKILL.md"))) {
194
194
  const ojmSkillDir = path.join(destDir, ".claude/skills/ojm-module-dev");
195
195
  fs.mkdirSync(ojmSkillDir, { recursive: true });
196
- // `wecom-daikaifa.md`(企业微信代开发:凭据清单 + 用户/开发/部署三视角)
197
- // 随之分发——它讲的是「怎么把一个已经写好的连接器接上真实套件」,
198
- // 业务工程拿到模板后自助完成对接要用,不能只留本仓。
199
- for (const name of ["SKILL.md", "manual.md", "README.md", "wecom-daikaifa.md"])
196
+ // 企微两本手册随之分发——它们讲的是「怎么把一个已经写好的连接器接上真实企业」,
197
+ // 业务工程拿到模板后自助完成对接要用,不能只留本仓:
198
+ // · `wecom-daikaifa.md` 企业微信代开发(服务商模板):凭据清单 + 用户/开发/部署三视角;
199
+ // · `wecom-internal-app.md` 企业内部应用(自建):五项 corp 凭据 + 验证并接入 + 共存。
200
+ for (const name of ["SKILL.md", "manual.md", "README.md", "wecom-daikaifa.md", "wecom-internal-app.md"])
200
201
  copyIfAbsent(path.join(cliDevkit, name), path.join(ojmSkillDir, name), report);
201
202
  }
202
203
  else {
@@ -20,6 +20,18 @@ export interface SharedDepEntry {
20
20
  asset?: string
21
21
  /** 硬共享:模块不得自带 */
22
22
  hard: boolean
23
+ /**
24
+ * 软共享但「不进宿主首屏」:宿主 shell 不预构建该资产、importmap 不列该键,
25
+ * 模块若 import 它则自行打包进模块产物(设计文档 §4.3 的「软共享缺席→模块自带
26
+ * 副本兜底」语义)。用于**仅模块使用、宿主与共享 chunk 永不 import**的重型依赖
27
+ * (编辑器 / 图表 / Markdown / 拖拽 …),避免把它们 5~6MB 的体量无谓铺进
28
+ * shell-dist 并要求公开链路多传一堆文件。宿主侧有真实 import 的共享依赖
29
+ * (antd / pro-components / icons 内部依赖的 motion / react-jss / dayjs /
30
+ * zustand 等)绝不能标 bundle —— 否则会被 antd 等共享 chunk 经 importmap 解析
31
+ * 不到而 404。判定依据:宿主 host.tsx + runtime.js 的传递 import 图,以及
32
+ * antd/pro-components/icons 的传递依赖图,都不含这些说明符。
33
+ */
34
+ bundle?: boolean
23
35
  }
24
36
 
25
37
  export const SHARED_DEPS: SharedDepEntry[] = [
@@ -58,7 +70,7 @@ export const SHARED_DEPS: SharedDepEntry[] = [
58
70
  * scopes 多版本共存兜底(C7)」——缺席时模块可自带副本而不硬失败。
59
71
  * 代价:多副本会重复约 5.4 MB 资产,故 scopes 只作兜底、默认仍走宿主。
60
72
  */
61
- { specifier: "@plane/editor", asset: "editor", hard: false },
73
+ { specifier: "@plane/editor", asset: "editor", hard: false, bundle: true },
62
74
  // 注:@rc-component/form(antd 6 Form 底层)曾尝试单例化以修 my-profile
63
75
  // 崩溃(React #130),假说未证实——多副本并非充分根因,已回退;见
64
76
  // docs/prd/202609010056-playground-full-modules-plan.md 差异项 D2。
@@ -108,10 +120,10 @@ export const SHARED_DEPS: SharedDepEntry[] = [
108
120
  { specifier: "dayjs/plugin/weekday", asset: "dayjs-plugin-weekday", hard: false },
109
121
  { specifier: "dayjs/plugin/weekOfYear", asset: "dayjs-plugin-weekOfYear", hard: false },
110
122
  { specifier: "dayjs/plugin/weekYear", asset: "dayjs-plugin-weekYear", hard: false },
111
- { specifier: "echarts", asset: "echarts", hard: false },
112
- { specifier: "echarts/charts", asset: "echarts-charts", hard: false },
113
- { specifier: "echarts/features.js", asset: "echarts-features", hard: false },
114
- { specifier: "echarts-for-react", asset: "echarts-for-react", hard: false },
123
+ { specifier: "echarts", asset: "echarts", hard: false, bundle: true },
124
+ { specifier: "echarts/charts", asset: "echarts-charts", hard: false, bundle: true },
125
+ { specifier: "echarts/features.js", asset: "echarts-features", hard: false, bundle: true },
126
+ { specifier: "echarts-for-react", asset: "echarts-for-react", hard: false, bundle: true },
115
127
  { specifier: "motion", asset: "motion", hard: false },
116
128
  { specifier: "motion/react", asset: "motion-react", hard: false },
117
129
  { specifier: "@dnd-kit/core", asset: "dnd-kit-core", hard: false },
@@ -127,6 +139,8 @@ export const SHARED_DEPS: SharedDepEntry[] = [
127
139
  { specifier: "react-error-boundary", asset: "react-error-boundary", hard: false },
128
140
  { specifier: "react-jss", asset: "react-jss", hard: false },
129
141
  { specifier: "clsx", asset: "clsx", hard: false },
142
+ // react-countup 只有 CJS 入口(main: build),其 require("react") 在模块自打包时会退化成
143
+ // 浏览器不可用的 require 桩 → 必须走宿主共享资产(shell 侧有 CJS→ESM 垫片)
130
144
  { specifier: "react-countup", asset: "react-countup", hard: false },
131
145
  { specifier: "simplebar-react", asset: "simplebar-react", hard: false },
132
146
  { specifier: "spin-delay", asset: "spin-delay", hard: false },
@@ -147,8 +161,8 @@ export const SHARED_DEPS: SharedDepEntry[] = [
147
161
  * 直接 import 它们,会命中前缀 external 但无 importmap 键 → 构建期深路径报错,
148
162
  * 属预期拦截(与 dayjs/plugin/* 同规则)。
149
163
  */
150
- { specifier: "vditor", asset: "vditor", hard: false },
151
- { specifier: "vditor/dist/index.css", asset: "vditor-index-css", hard: false },
164
+ { specifier: "vditor", asset: "vditor", hard: false, bundle: true },
165
+ { specifier: "vditor/dist/index.css", asset: "vditor-index-css", hard: false, bundle: true },
152
166
  ];
153
167
 
154
168
  /** 判断一个裸说明符是否命中共享表(命中主包前缀的深路径也按共享处理) */
@@ -161,7 +175,7 @@ export function isSharedDep(id: string): boolean {
161
175
  */
162
176
  export function generateShellEntries(): { name: string, pkg: string }[] {
163
177
  return SHARED_DEPS
164
- .filter(dep => dep.asset && dep.specifier !== "@oj-module/runtime")
178
+ .filter(dep => dep.asset && !dep.bundle && dep.specifier !== "@oj-module/runtime")
165
179
  .map(dep => ({ name: dep.asset!, pkg: dep.specifier }));
166
180
  }
167
181
 
@@ -172,9 +186,13 @@ export function generateShellEntries(): { name: string, pkg: string }[] {
172
186
  export function generateImportmap(baseUrl = ""): Record<string, string> {
173
187
  const map: Record<string, string> = {};
174
188
  for (const dep of SHARED_DEPS) {
175
- if (dep.asset) {
189
+ if (dep.asset && !dep.bundle) {
176
190
  map[dep.specifier] = `${baseUrl}/assets/${dep.asset}.js`;
177
191
  }
178
192
  }
179
193
  return map;
180
194
  }
195
+
196
+ export function isPrebuiltDep(id: string): boolean {
197
+ return SHARED_DEPS.some(dep => !dep.bundle && (id === dep.specifier || id.startsWith(`${dep.specifier}/`)));
198
+ }
package/src/site-base.ts CHANGED
@@ -100,7 +100,7 @@ export function rewriteIndexHtml(html: string, siteBase: string | undefined, api
100
100
  out = rewriteImportmap(out, base);
101
101
  // 属性值:只要以单个 "/" 开头(排除 "//host")。importmap 是脚本正文、非属性,
102
102
  // 上一步已处理,二者不重叠。
103
- out = out.replace(/(\b(?:href|src)=")(\/[^/"][^"]*|\/")/g, (_m, attr: string, url: string) =>
103
+ out = out.replace(/(\b(?:href|src|data-ojm-entry)=")(\/[^/"][^"]*|\/")/g, (_m, attr: string, url: string) =>
104
104
  `${attr}${withSiteBase(url, base)}`);
105
105
  globals.push(`window.__OJ_BASE__=${JSON.stringify(base)};`);
106
106
  }
@@ -56,8 +56,13 @@ vars:
56
56
  # 故意不读请求头 Host(防伪造 Host 牵引出站):不配 = fail-closed,不会替你猜。
57
57
  # canonical_origin: "https://your-site.example.com"
58
58
 
59
- # ---- 企业微信代开发(服务商侧,部署级常量)----
60
- # 一个部署 = 一个服务商账号下的一套代开发模板,所以这 8 个值是「部署」维度,不进 DB。
59
+ # ---- 企业微信(部署级常量,不进 DB)----
60
+ # 两种应用来源,**可同时启用**(docs/plans/20261101-2044-wecom-internal-app-plan.md):
61
+ # · 代开发(suite):一套代开发模板,企业扫码授权即接入,一套部署可服务多家企业;
62
+ # · 企业内部应用 / 自建应用(corp):企业自建应用,一套部署只服务一家企业。
63
+ # 两套的企业侧数据链路相同(permanent_code 就是当 corpsecret 用的),差别只在凭据来源与回调。
64
+ # 从代开发迁到自建时请把旧的 suite 变量清空;两套同时生效须显式开 wecom_allow_coexist。
65
+ # 一个部署 = 一个服务商账号下的一套代开发模板(或一家企业的自建应用),所以这些值是「部署」维度,不进 DB。
61
66
  # 「哪个企业装了哪个应用」不写在这里:企微 create_auth 回调推 AuthCode → 换永久授权码 →
62
67
  # 落 wecom_app_profile(主键含 agent_id,同一企业测试/正式两个应用天然并存),
63
68
  # 新增企业只需企业管理员扫码授权,**不改配置、不重启**。
@@ -74,18 +79,35 @@ vars:
74
79
  # wecom_provider_id 服务商的 corpid(服务商自己的,不是企业 corpid);**位置:服务商后台「服务商信息」页显示的 CorpID**
75
80
  # wecom_provider_secret 服务商 provider_secret;**位置:服务商后台 → 应用管理 → 通用开发参数(点「编辑」)→ ProviderSecret 字段**,
76
81
  # **不在「服务商信息」页**(那里只显示 CorpID),也**不是** suite_secret(suite 那个是另一套)。
77
- # wecom_callback_token 「Token」(回调验签用)
78
- # wecom_encoding_aes_key 「EncodingAESKey」(43 字符,回调解密用)
79
- # wecom_enc_key permanent_code 落库加密密钥(hex/base64,16 或 32 字节;建议 oj secret keygen)
82
+ # wecom_callback_token 代开发「指令回调」的 Token(回调验签用)
83
+ # wecom_encoding_aes_key 代开发「指令回调」的 EncodingAESKey(43 字符,回调解密用)
84
+ # wecom_enc_key permanent_code / 应用 Secret 的落库加密密钥(hex/base64,16 或 32 字节;建议 oj secret keygen)
80
85
  # wecom_base_url 连接器基址(绝对地址,无尾斜杠,**这是基址、不是回调 URL**),如 https://site.example.com/api/auth/wecom
81
- # → 企微后台「指令回调URL」填 <base>/event(必须带 /event,裸 <base> 无 handler 会 404);
86
+ # → 回调 URL 填 <base>/event(必须带 /event,裸 <base> 无 handler 会 404);
82
87
  # 网页授权 redirect_uri 用 <base>/callback
88
+ #
89
+ # 企业内部应用(corp)——一套部署一家企业:
90
+ # wecom_corp_id 企微后台「我的企业 → 企业信息 → 企业ID」;同时是租户键
91
+ # wecom_corp_secret 应用详情页「Secret」;**换过后必须到 /system/wecom 重新「验证并接入」**
92
+ # wecom_corp_agent_id 应用详情页「AgentId」
93
+ # wecom_corp_callback_token 「管理工具 → 通讯录同步 → 接收事件服务器」的 Token(可选;不配 = 不做事件增量)
94
+ # wecom_corp_encoding_aes_key 同上 → EncodingAESKey(可选)
95
+ # ⚠ 自建应用**收不到**成员通讯录变更事件(官方 90970),事件增量只能靠上面这组「通讯录同步」回调,
96
+ # 且企微要求该 URL 在企业主体域名下——所以它只适用于私有化单企业部署。
97
+ #
98
+ # wecom_allow_coexist 两套凭据都配齐时必须显式写 'true' 才允许共存(否则 fail-fast)
83
99
  wecom_suite_id: ''
84
100
  wecom_suite_secret: ''
85
101
  wecom_provider_id: ''
86
102
  wecom_provider_secret: ''
87
103
  wecom_callback_token: ''
88
104
  wecom_encoding_aes_key: ''
105
+ wecom_corp_id: ''
106
+ wecom_corp_secret: ''
107
+ wecom_corp_agent_id: ''
108
+ wecom_corp_callback_token: ''
109
+ wecom_corp_encoding_aes_key: ''
110
+ wecom_allow_coexist: ''
89
111
  wecom_enc_key: ''
90
112
  wecom_base_url: ''
91
113
 
@@ -20,9 +20,9 @@
20
20
  # `pnpm dev` 时忽略产物,不进发布包、也不进用户工程。守卫:
21
21
  # tests/cli/release-manifest.test.ts 逐条核对清单条目落在 tarball 内。
22
22
  #
23
- # 注意 web/src/{demo,home}/__client 不在清单:其 api 模块(api/src/demo、
24
- # api/src/home)是 demo-only 不进脚手架,client 生成物留着会指向不存在的端点,
25
- # 故 demo/home 只列页面源码(entry/locales/pages),不列整棵模块目录。
23
+ # 注意 web/src/{home}/__client 不在清单:其 api 模块(api/src/home)是 demo-only
24
+ # 不进脚手架,client 生成物留着会指向不存在的端点,故 home 只列页面源码
25
+ # (entry/locales/pages),不列整棵模块目录。
26
26
  include:
27
27
  # —— 工程级文件 ——
28
28
  - README.md
@@ -101,12 +101,9 @@ include:
101
101
  - src/personal-center/api
102
102
  - src/system/api
103
103
  - src/web/api
104
- # —— web 前端模块(demo/home 只列页面源码,__client 见顶部说明)——
104
+ # —— web 前端模块(home 只列页面源码,__client 见顶部说明)——
105
105
  - src/_platform/web
106
106
  - src/auth/web
107
- - src/demo/web/entry.ts
108
- - src/demo/web/locales
109
- - src/demo/web/pages
110
107
  - src/home/web/entry.ts
111
108
  - src/home/web/locales
112
109
  - src/home/web/pages