openxiangda-cli 2.0.0-alpha.98 → 2.0.0

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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -8
  3. package/bin/run.js +2 -11
  4. package/dist/base.d.ts +30 -6
  5. package/dist/base.d.ts.map +1 -1
  6. package/dist/base.js +120 -22
  7. package/dist/base.js.map +1 -1
  8. package/dist/commands/accept.d.ts +28 -0
  9. package/dist/commands/accept.d.ts.map +1 -0
  10. package/dist/commands/accept.js +21 -0
  11. package/dist/commands/accept.js.map +1 -0
  12. package/dist/commands/admin.d.ts +32 -0
  13. package/dist/commands/admin.d.ts.map +1 -0
  14. package/dist/commands/admin.js +27 -0
  15. package/dist/commands/admin.js.map +1 -0
  16. package/dist/commands/auth.d.ts +30 -0
  17. package/dist/commands/auth.d.ts.map +1 -0
  18. package/dist/commands/auth.js +13 -0
  19. package/dist/commands/auth.js.map +1 -0
  20. package/dist/commands/cancel.d.ts +30 -0
  21. package/dist/commands/cancel.d.ts.map +1 -0
  22. package/dist/commands/cancel.js +12 -0
  23. package/dist/commands/cancel.js.map +1 -0
  24. package/dist/commands/check.d.ts +12 -3
  25. package/dist/commands/check.d.ts.map +1 -1
  26. package/dist/commands/check.js +21 -4
  27. package/dist/commands/check.js.map +1 -1
  28. package/dist/commands/context.d.ts +27 -0
  29. package/dist/commands/context.d.ts.map +1 -0
  30. package/dist/commands/context.js +10 -0
  31. package/dist/commands/context.js.map +1 -0
  32. package/dist/commands/create.d.ts +20 -3
  33. package/dist/commands/create.d.ts.map +1 -1
  34. package/dist/commands/create.js +396 -22
  35. package/dist/commands/create.js.map +1 -1
  36. package/dist/commands/deploy.d.ts +13 -3
  37. package/dist/commands/deploy.d.ts.map +1 -1
  38. package/dist/commands/deploy.js +17 -16
  39. package/dist/commands/deploy.js.map +1 -1
  40. package/dist/commands/dev.d.ts +10 -3
  41. package/dist/commands/dev.d.ts.map +1 -1
  42. package/dist/commands/dev.js +7 -4
  43. package/dist/commands/dev.js.map +1 -1
  44. package/dist/commands/docs.d.ts +30 -0
  45. package/dist/commands/docs.d.ts.map +1 -0
  46. package/dist/commands/docs.js +20 -0
  47. package/dist/commands/docs.js.map +1 -0
  48. package/dist/commands/login.d.ts +8 -3
  49. package/dist/commands/login.d.ts.map +1 -1
  50. package/dist/commands/login.js +1 -1
  51. package/dist/commands/login.js.map +1 -1
  52. package/dist/commands/logs.d.ts +10 -3
  53. package/dist/commands/logs.d.ts.map +1 -1
  54. package/dist/commands/logs.js +7 -3
  55. package/dist/commands/logs.js.map +1 -1
  56. package/dist/commands/retry.d.ts +30 -0
  57. package/dist/commands/retry.d.ts.map +1 -0
  58. package/dist/commands/retry.js +12 -0
  59. package/dist/commands/retry.js.map +1 -0
  60. package/dist/commands/rollback.d.ts +11 -3
  61. package/dist/commands/rollback.d.ts.map +1 -1
  62. package/dist/commands/rollback.js +9 -3
  63. package/dist/commands/rollback.js.map +1 -1
  64. package/dist/commands/skill.d.ts +9 -3
  65. package/dist/commands/skill.d.ts.map +1 -1
  66. package/dist/commands/skill.js +9 -2
  67. package/dist/commands/skill.js.map +1 -1
  68. package/dist/commands/spec.d.ts +42 -0
  69. package/dist/commands/spec.d.ts.map +1 -0
  70. package/dist/commands/spec.js +123 -0
  71. package/dist/commands/spec.js.map +1 -0
  72. package/dist/commands/start.d.ts +28 -0
  73. package/dist/commands/start.d.ts.map +1 -0
  74. package/dist/commands/start.js +18 -0
  75. package/dist/commands/start.js.map +1 -0
  76. package/dist/commands/status.d.ts +9 -3
  77. package/dist/commands/status.d.ts.map +1 -1
  78. package/dist/commands/status.js +10 -3
  79. package/dist/commands/status.js.map +1 -1
  80. package/dist/commands/stop.d.ts +28 -0
  81. package/dist/commands/stop.d.ts.map +1 -0
  82. package/dist/commands/stop.js +18 -0
  83. package/dist/commands/stop.js.map +1 -0
  84. package/dist/create-workspace.d.ts +36 -0
  85. package/dist/create-workspace.d.ts.map +1 -1
  86. package/dist/create-workspace.js +366 -8
  87. package/dist/create-workspace.js.map +1 -1
  88. package/dist/studio-events.d.ts +9 -0
  89. package/dist/studio-events.d.ts.map +1 -0
  90. package/dist/studio-events.js +30 -0
  91. package/dist/studio-events.js.map +1 -0
  92. package/package.json +15 -6
  93. package/template/AGENTS.md +25 -20
  94. package/template/README.md +31 -12
  95. package/template/apps/web/e2e/README.md +7 -0
  96. package/template/apps/web/package.json +4 -2
  97. package/template/apps/web/playwright.config.ts +4 -3
  98. package/template/apps/web/src/document.css +5 -0
  99. package/template/apps/web/src/main.tsx +48 -3
  100. package/template/apps/web/src/user-end/tailwind.css +5 -0
  101. package/template/apps/web/test/loopback.test.ts +8 -2
  102. package/template/apps/web/test/standard-user-surfaces.typecheck.tsx +27 -0
  103. package/template/apps/web/vite.config.ts +5 -2
  104. package/template/appspec/app.md +56 -0
  105. package/template/openxiangda.config.ts +50 -25
  106. package/template/package.json +4 -4
  107. package/template/packages/contracts/package.json +1 -1
  108. package/template/packages/contracts/src/generated.ts +143 -2
  109. package/template/apps/server/Dockerfile +0 -20
  110. package/template/apps/server/package.json +0 -27
  111. package/template/apps/server/src/app.module.ts +0 -43
  112. package/template/apps/server/src/main.ts +0 -5
  113. package/template/apps/server/test/smoke.test.ts +0 -16
  114. package/template/apps/server/tsconfig.json +0 -9
  115. package/template/apps/web/data-api-live.e2e.html +0 -15
  116. package/template/apps/web/e2e/client-fixture.ts +0 -5
  117. package/template/apps/web/e2e/data-api-live-fixture.tsx +0 -146
  118. package/template/apps/web/e2e/data-api-live.spec.ts +0 -138
  119. package/template/apps/web/e2e/field-protocol-fixture.tsx +0 -347
  120. package/template/apps/web/e2e/field-protocol.spec.ts +0 -186
  121. package/template/apps/web/e2e/resources.spec.ts +0 -285
  122. package/template/apps/web/e2e/workflow.spec.ts +0 -329
  123. package/template/apps/web/field-protocol.e2e.html +0 -15
  124. package/template/apps/web/scripts/check.mjs +0 -43
  125. package/template/apps/web/test/contracts.test.ts +0 -99
  126. package/template/scripts/verify-template-budget.mjs +0 -49
@@ -1,26 +1,45 @@
1
- # OpenXiangda 2.0 应用模板
1
+ # OpenXiangda 2.0 应用
2
2
 
3
- 这是干净的资源 CRUD 模板:Vite + React Router + Refine Core + Ant Design 前端、NestJS 平台启动层,以及平台 Data/AuthZ 声明。
3
+ 本项目使用精确版本的 openxiangda 根包。默认包含 Web 和共享契约,普通 CRUD、标准审批与通知由平台执行;真实业务动作才按需启用 Nest。
4
+
5
+ ## 开始
4
6
 
5
7
  ```bash
8
+ pnpm install --frozen-lockfile
9
+ pnpm openxiangda context --json
6
10
  pnpm openxiangda dev
7
11
  ```
8
12
 
9
- 当前应用只直接依赖一个精确版本的 `openxiangda` 根包,并在 lockfile 中锁定其内部实现包。创建另一个应用时使用当前安装的 OpenXiangda 2.0 Skill 给出的精确根包版本,不使用 `latest`、`alpha` 或全局命令。
13
+ 依赖已安装时无需重复安装。开发连接已绑定的远端测试数据,终端和页面显示实际环境。缺少账号或绑定时按命令错误提示处理;不要调用旧版全局 CLI。
14
+
15
+ ## 阅读资料
16
+
17
+ ```bash
18
+ pnpm openxiangda docs
19
+ pnpm openxiangda docs development
20
+ pnpm openxiangda docs application-foundation
21
+ pnpm openxiangda docs frontend
22
+ pnpm openxiangda docs data-authz
23
+ ```
24
+
25
+ 资料来自当前根包;长主题支持 --section <章节ID>。AGENTS.md 的平台段说明基本约定,项目自有内容保留在该段之外。AI Skill 按任务读取同源资料。
26
+
27
+ ## 声明与页面
28
+
29
+ 业务模型和页面选择保存在 modules,应用根配置负责装配。模型不隐含导航或写授权。字段与标准 CRUD 使用平台组件;PC 控件使用 antd,移动使用 openxiangda/mobile。应用负责最小文档重置和局部样式,不直接导入 antd-mobile 全局重置。
30
+
31
+ apps/server 仅在启用后端时初始化。后续 check/dev 保留业务源码,普通 CRUD 不增加 controller。
10
32
 
11
- `openxiangda.config.ts` 的 `data.resources` 中只声明资源 `code`、`name`、`fields` 和可选列表/布局/数据策略。每个字段一次写完语义 `type`、`label`、`required`、列表/筛选标记、`options`/`source`/`file` 与 `access`;不要再写 `schema`、`surface`、PostgreSQL 类型、资源 capabilities、资源 fieldPolicies 或 `platform/data` 模块。编译器据此生成标准后台列表、筛选、表单、详情、审计、文件上传、独立移动端页面、Data API 查询计划和 AI Schema。
33
+ ## 检查与发布
12
34
 
13
- 成员、部门分别声明 `user.single`/`user.multiple` `department.single`/`department.multiple`;同应用动态资源声明 `resource-ref.single`/`resource-ref.multiple` 及编译期固定的 `source`。这些字段都保存完整 `{label,value,...}` 显示快照,资源引用还保存 `resourceCode` 和声明的 `snapshot` 字段;查询和权限以稳定 `value` 为比较键。资源来源的 `labelField` 只能引用 `text.short` 或 `text.long`;流水号可用于搜索、描述和快照,不能作为显示标签。需要目录选择器的业务角色必须显式拥有 `app:<app-code>:directory:read` 能力。用 `resourceCapabilityCodes` 生成资源 CRUD 权限,用 `currentUserDataPolicy` 声明唯一的当前用户行规则。
35
+ 只检查时运行 `pnpm openxiangda check`;需要发布测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建。纯前端无需 Docker,启用后端时才构建官方 Dockerfile 镜像。
14
36
 
15
- 字段组件、CRUD Renderer、Shell、Runtime Data/Directory/File/App Client 都由 `openxiangda/react`、`openxiangda/field-kit` `openxiangda/core` 统一提供,应用模板不复制这些实现。Nest 只在需要复杂事务或外部集成时增加 App API。标准客户端显式携带当前环境、使用同源 Cookie;业务代码不读取或持久化 Token、平台内部身份标识与授权结果。
37
+ 运行 status/logs 确认平台结果并完成真实角色验收。生产使用 `pnpm openxiangda deploy --environment production --from <成功测试运行ID>`;`--dry-run` 只预览。完整发布、取消、恢复和回滚说明使用 `pnpm openxiangda docs delivery`。
16
38
 
17
- 前端一次读取当前登录用户在当前环境的完整应用角色并集和 capability 做展示保护,不提供角色选择器,也不发送 RoleSession header。服务端始终以同一角色并集权威执行操作、字段、行权限和 RLS。字段默认继承资源 read/create/update;`access` 数组为 all-of,`false` 为明确拒绝,无权字段在提交前从 payload 删除。
39
+ ## MCP
18
40
 
19
41
  ```bash
20
- pnpm openxiangda check
21
- pnpm openxiangda deploy
22
- pnpm openxiangda status
23
- pnpm openxiangda logs
42
+ pnpm exec openxiangda --mcp-stdio --cwd <项目绝对路径>
24
43
  ```
25
44
 
26
- `deploy` 使用本模板的 `apps/server/Dockerfile` 自动构建并推送 `linux/amd64` 镜像,再把不可变 digest 写入 AppPackage。纯 CRUD 应用可以保持 backend disabled;复杂事务再启用 Nest
45
+ 先读 workspace_context,再按需读取 docs_read contract_describe;升级包后重启连接。登录、创建与长期开发进程继续使用 CLI
@@ -0,0 +1,7 @@
1
+ # 本应用的浏览器验收
2
+
3
+ 在此目录按 AppSpec 的实际验收条件添加 `*.spec.ts`。模板没有预先通过的业务用例;尚未编写用例时,`pnpm test:e2e` 会提示未发现测试。
4
+
5
+ 先启动完整连接开发环境,确认目标是测试环境,再通过 `OPENXIANGDA_E2E_BASE_URL` 指定它的 Web 地址并运行 `pnpm test:e2e`。使用平台正式提供的测试身份,分别验证允许与拒绝路径、PC 与移动页面、操作后数据及历史记录。登录信息不提交 Git。
6
+
7
+ 测试页面与接口属于当前应用。模拟响应可用于局部交互回归,应明确记录范围;它不能证明平台权限或真实业务通过。工具链维护者的通用组件夹具不会复制到这里。
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
8
- "check": "tsc -p tsconfig.json --noEmit && node scripts/check.mjs",
8
+ "check": "tsc -p tsconfig.json --noEmit",
9
9
  "test": "tsx --test test/*.test.ts",
10
10
  "test:e2e": "playwright test",
11
11
  "build": "tsc -p tsconfig.json --noEmit && vite build && node scripts/verify-build.mjs"
@@ -14,17 +14,19 @@
14
14
  "@ant-design/icons": "6.2.3",
15
15
  "@refinedev/core": "5.0.12",
16
16
  "antd": "6.4.3",
17
- "openxiangda": "2.0.0-alpha.10",
17
+ "openxiangda": "2.0.0",
18
18
  "react": "19.2.8",
19
19
  "react-dom": "19.2.8",
20
20
  "react-router-dom": "7.8.2"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@playwright/test": "1.62.1",
24
+ "@tailwindcss/vite": "4.3.3",
24
25
  "@types/node": "24.13.3",
25
26
  "@types/react": "19.2.14",
26
27
  "@types/react-dom": "19.2.3",
27
28
  "@vitejs/plugin-react": "4.7.0",
29
+ "tailwindcss": "4.3.3",
28
30
  "tsx": "4.23.12",
29
31
  "typescript": "5.9.3",
30
32
  "vite": "7.1.3"
@@ -6,7 +6,8 @@ const baseURL =
6
6
 
7
7
  export default defineConfig({
8
8
  testDir: './e2e',
9
- workers: 2,
9
+ // 在 e2e/ 中添加本应用的真实业务验收,测试账户与环境由测试代码明确选择。
10
+ workers: 1,
10
11
  use: { baseURL, trace: 'retain-on-failure' },
11
12
  webServer: process.env.OPENXIANGDA_E2E_BASE_URL
12
13
  ? undefined
@@ -16,7 +17,7 @@ export default defineConfig({
16
17
  reuseExistingServer: false,
17
18
  env: {
18
19
  OPENXIANGDA_WEB_PORT: String(e2ePort),
19
- OPENXIANGDA_ENVIRONMENT_KEY: 'production',
20
+ OPENXIANGDA_ENVIRONMENT_KEY: 'preproduction',
20
21
  OPENXIANGDA_DEV_PROXY:
21
22
  process.env.OPENXIANGDA_E2E_PLATFORM_URL ||
22
23
  'http://127.0.0.1:7001',
@@ -30,7 +31,7 @@ function resolveE2ePort() {
30
31
  const port = Number(configured);
31
32
  if (!Number.isInteger(port) || port < 1024 || port > 65535) {
32
33
  throw new Error(
33
- 'OPENXIANGDA_E2E_PORT must be an integer between 1024 and 65535'
34
+ 'OPENXIANGDA_E2E_PORT 必须是 1024 65535 之间的整数'
34
35
  );
35
36
  }
36
37
  return port;
@@ -0,0 +1,5 @@
1
+ /* The application owns the document reset; platform CSS stays scoped. */
2
+ html, body, #root {
3
+ min-height: 100%;
4
+ margin: 0;
5
+ }
@@ -1,21 +1,66 @@
1
1
  import React from 'react';
2
2
  import ReactDOM from 'react-dom/client';
3
- import { OpenXiangdaApplication } from 'openxiangda/react';
3
+ import {
4
+ defineApplicationContributions,
5
+ DefaultDesktopApplicationLoginSurface,
6
+ DefaultMobileApplicationLoginSurface,
7
+ OpenXiangdaApplication,
8
+ type ApplicationLoginSurfaceProps,
9
+ } from 'openxiangda/react';
4
10
  import {
5
11
  appCode,
6
12
  appName,
13
+ appPerspectives,
14
+ appRoutes,
15
+ routeManifest,
16
+ authenticationSurfaces,
17
+ anonymousPublicAccess,
18
+ adminNavigation,
19
+ adminAccess,
20
+ adminPages,
7
21
  resourceDefinitions,
8
- workflowCodes,
22
+ workflowDefinitions,
9
23
  } from '../../../packages/contracts/src/generated.js';
24
+ import './document.css';
25
+ import './user-end/tailwind.css';
10
26
  import 'openxiangda/react/styles.css';
11
27
 
28
+ const ApplicationHome = () => <main>OpenXiangda 应用</main>;
29
+ const ApplicationHomeMobile = () => <main>OpenXiangda 移动应用</main>;
30
+ const BrandedDesktopLogin = (props: ApplicationLoginSurfaceProps) => (
31
+ <DefaultDesktopApplicationLoginSurface {...props} />
32
+ );
33
+ const BrandedMobileLogin = (props: ApplicationLoginSurfaceProps) => (
34
+ <DefaultMobileApplicationLoginSurface {...props} />
35
+ );
36
+
37
+ const applicationContributions = defineApplicationContributions(
38
+ { routes: appRoutes, authenticationSurfaces },
39
+ {
40
+ pages: {
41
+ applicationHome: ApplicationHome,
42
+ applicationHomeMobile: ApplicationHomeMobile,
43
+ },
44
+ authentication: {
45
+ applicationLogin: BrandedDesktopLogin,
46
+ applicationLoginMobile: BrandedMobileLogin,
47
+ },
48
+ },
49
+ );
12
50
  ReactDOM.createRoot(document.getElementById('root')!).render(
13
51
  <React.StrictMode>
14
52
  <OpenXiangdaApplication
15
53
  appCode={appCode}
16
54
  appName={appName}
55
+ adminAccess={adminAccess}
56
+ adminNavigation={adminNavigation}
57
+ adminPages={adminPages}
58
+ routeManifest={routeManifest}
59
+ contributions={applicationContributions}
60
+ publicAccess={anonymousPublicAccess}
61
+ perspectives={appPerspectives}
17
62
  resourceDefinitions={resourceDefinitions}
18
- workflows={workflowCodes}
63
+ workflows={workflowDefinitions}
19
64
  />
20
65
  </React.StrictMode>,
21
66
  );
@@ -0,0 +1,5 @@
1
+ /* User-page utilities only; the application owns its document reset. */
2
+ @import "tailwindcss/theme.css";
3
+ @import "tailwindcss/utilities.css";
4
+
5
+ @source "../";
@@ -1,14 +1,20 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import { spawn } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
3
4
  import { createServer } from 'node:net';
4
5
  import { networkInterfaces } from 'node:os';
5
- import { resolve } from 'node:path';
6
+ import { dirname, join, resolve } from 'node:path';
6
7
  import test from 'node:test';
7
8
 
9
+ const require = createRequire(import.meta.url);
10
+
8
11
  test('Vite is reachable on loopback and not on a LAN interface', async t => {
9
12
  const port = await freePort();
10
13
  const root = resolve(import.meta.dirname, '..');
11
- const child = spawn('pnpm', ['exec', 'vite', '--port', String(port), '--strictPort'], {
14
+ const vitePackagePath = require.resolve('vite/package.json');
15
+ const vitePackage = require(vitePackagePath) as { bin: { vite: string } };
16
+ const viteBin = join(dirname(vitePackagePath), vitePackage.bin.vite);
17
+ const child = spawn(process.execPath, [viteBin, '--port', String(port), '--strictPort'], {
12
18
  cwd: root,
13
19
  stdio: 'ignore',
14
20
  env: {
@@ -0,0 +1,27 @@
1
+ import {
2
+ defineApplicationContributions,
3
+ type StandardApplicationTodoCenterProps,
4
+ type StandardUserPageFrameProps,
5
+ type StandardUserSurfaceContributions,
6
+ } from 'openxiangda/react';
7
+
8
+ function Frame({ children }: StandardUserPageFrameProps) {
9
+ return <>{children}</>;
10
+ }
11
+
12
+ function Todo({ items }: StandardApplicationTodoCenterProps) {
13
+ return <>{items.length}</>;
14
+ }
15
+
16
+ const standardUserSurfaces = {
17
+ frame: { desktop: Frame, mobile: Frame },
18
+ applicationTodoCenter: { desktop: Todo, mobile: Todo },
19
+ } satisfies StandardUserSurfaceContributions;
20
+
21
+ const contributions = defineApplicationContributions({}, {
22
+ pages: {},
23
+ standardUserSurfaces,
24
+ });
25
+
26
+ const exportedFrame = contributions.standardUserSurfaces?.frame.desktop;
27
+ void exportedFrame;
@@ -1,4 +1,5 @@
1
1
  import react from '@vitejs/plugin-react';
2
+ import tailwindcss from '@tailwindcss/vite';
2
3
  import { defineConfig } from 'vite';
3
4
 
4
5
  const connectedProxy =
@@ -6,8 +7,11 @@ const connectedProxy =
6
7
  const port = Number(process.env.OPENXIANGDA_WEB_PORT || 5173);
7
8
 
8
9
  export default defineConfig({
9
- plugins: [react()],
10
+ plugins: [react(), tailwindcss()],
10
11
  base: './',
12
+ optimizeDeps: {
13
+ entries: ['index.html'],
14
+ },
11
15
  server: {
12
16
  host: '127.0.0.1',
13
17
  port,
@@ -24,7 +28,6 @@ export default defineConfig({
24
28
  manualChunks: {
25
29
  react: ['react', 'react-dom', 'react-router-dom'],
26
30
  refine: ['@refinedev/core'],
27
- antd: ['antd', '@ant-design/icons'],
28
31
  },
29
32
  },
30
33
  },
@@ -0,0 +1,56 @@
1
+ ---
2
+ schema: openxiangda.appspec/app/v1
3
+ app: "openxiangda-application"
4
+ title: "OpenXiangda 应用"
5
+ status: active
6
+ ---
7
+ # OpenXiangda 应用
8
+
9
+ 本文件是当前有效设计的总纲与目录。新应用先完成产品发现、详细设计与实际确认,形成评审基线后再制定实施计划;按 docs product-design 在 AppSpec 中建立相关材料。正式变更关联 ChangeSpec;测试发布前核对设计与验收计划,生产晋级前核对实际验收。资源、字段、权限和动作的实现以
10
+ `openxiangda.config.ts` 与实时编译合同为准。
11
+
12
+ ## 业务目标
13
+
14
+ <!-- 用业务语言说明应用解决的问题和可观察结果。 -->
15
+
16
+ ## 角色
17
+
18
+ <!-- 只记录业务角色及其目标;稳定 role code 在能力规格中引用。 -->
19
+
20
+ ## 范围
21
+
22
+ ### 包含
23
+
24
+ ### 不包含
25
+
26
+ ## 设计资料目录
27
+
28
+ <!-- 引用产品、旅程、页面、视觉、权限、架构与评审的稳定 ID;详细规则各有唯一归属。 -->
29
+
30
+ ## 能力目录
31
+
32
+ <!-- 复杂业务再在 capabilities/ 下增加 CAP-*;简单应用的稳定规则直接写在本文件。 -->
33
+
34
+ ## 术语
35
+
36
+ ## 跨能力约束
37
+
38
+ ## 架构与数据关系
39
+
40
+ <!-- 写明领域边界、核心模型关系、平台能力与自定义动作的归属、状态转换与不变量。 -->
41
+
42
+ ## 业务任务与页面
43
+
44
+ <!-- 按用户任务规划管理端、PC、移动入口与数据关系;辅助表不必有页面。 -->
45
+
46
+ ## 权限矩阵与确认
47
+
48
+ <!-- 记录角色、页面、操作、行范围、字段和多角色组合;写明需求依据与未明确的边界。 -->
49
+
50
+ ## 性能与容量预算
51
+
52
+ <!-- 记录数据量及增长、并发、分页与索引、请求次数、延迟目标和测量口径;区分估算、目标与实测。 -->
53
+
54
+ ## 未确认问题
55
+
56
+ - 无。
@@ -1,30 +1,55 @@
1
- import { defineOpenXiangdaApp } from 'openxiangda/config';
1
+ import { defineAdminNavigation, defineOpenXiangdaApp } from 'openxiangda/config';
2
2
 
3
- /** Clean application scaffold: declare resources and fields, then generate CRUD. */
3
+ /** Start with business modules; select standard CRUD only for the models that need it. */
4
4
  export default defineOpenXiangdaApp({
5
- schemaVersion: 3,
6
5
  app: { code: 'openxiangda-application', name: 'OpenXiangda 应用' },
7
- frontend: { root: 'apps/web', routes: [] },
8
- backend: {
9
- root: 'apps/server',
10
- runtime: 'node',
11
- framework: 'nestjs',
12
- enabled: false,
13
- isolation: 'shared',
14
- resourceProfile: 'light',
15
- secrets: [],
16
- operations: [],
6
+ frontend: {
7
+ root: 'apps/web',
8
+ devicePolicy: {
9
+ kind: 'viewport-family',
10
+ mobileMaxWidthPx: 900,
11
+ desktopMinWidthPx: 901,
12
+ },
13
+ routes: [
14
+ {
15
+ code: 'application-home',
16
+ path: '/home',
17
+ label: '应用首页',
18
+ surface: 'user',
19
+ },
20
+ {
21
+ code: 'application-home-mobile',
22
+ path: '/m/home',
23
+ label: '移动首页',
24
+ surface: 'user',
25
+ },
26
+ ],
27
+ authentication: {
28
+ accountMode: 'existing-platform-users-only',
29
+ registration: { mode: 'reject' },
30
+ methods: [
31
+ {
32
+ code: 'password',
33
+ type: 'password',
34
+ label: '账号密码登录',
35
+ presentation: 'primary',
36
+ required: true,
37
+ },
38
+ ],
39
+ surfaces: {
40
+ desktop: {
41
+ routeCode: 'application-login',
42
+ path: '/login',
43
+ defaultRouteCode: 'application-home',
44
+ },
45
+ mobile: {
46
+ routeCode: 'application-login-mobile',
47
+ path: '/m/login',
48
+ defaultRouteCode: 'application-home-mobile',
49
+ },
50
+ },
51
+ },
52
+ admin: { navigation: defineAdminNavigation([]) },
17
53
  },
18
- platform: { root: 'platform' },
19
- authz: {
20
- capabilities: [],
21
- roles: [],
22
- scopeDimensions: [],
23
- scopeSources: [],
24
- dataPolicies: [],
25
- authorizationTransitions: [],
26
- },
27
- data: { resources: [] },
28
- events: { schemas: [], subscriptions: [], timers: [], dateTriggers: [] },
29
- workflows: { definitions: [], bindings: [], activations: [], providers: [], editableParameters: [] },
54
+ modules: [],
30
55
  });
@@ -2,6 +2,7 @@
2
2
  "name": "openxiangda-application",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "type": "module",
5
6
  "packageManager": "pnpm@10.15.1",
6
7
  "engines": {
7
8
  "node": ">=22.12"
@@ -11,18 +12,17 @@
11
12
  "prepare": "pnpm workspace:build",
12
13
  "workspace:build": "pnpm --filter @app/contracts build",
13
14
  "predev": "pnpm workspace:build",
14
- "dev": "pnpm --parallel --filter './apps/*' dev",
15
+ "dev": "openxiangda dev",
15
16
  "dev:web": "pnpm --filter @app/web dev",
16
- "dev:server": "pnpm --filter @app/server dev",
17
17
  "precheck": "pnpm workspace:build",
18
- "check": "pnpm --recursive check && node scripts/verify-template-budget.mjs",
18
+ "check": "pnpm --recursive check",
19
19
  "pretest": "pnpm workspace:build",
20
20
  "test": "pnpm --recursive test",
21
21
  "test:e2e": "pnpm --filter @app/web test:e2e",
22
22
  "build": "pnpm --recursive build"
23
23
  },
24
24
  "devDependencies": {
25
- "openxiangda": "2.0.0-alpha.10",
25
+ "openxiangda": "2.0.0",
26
26
  "typescript": "5.9.3"
27
27
  }
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app/contracts",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,7 +1,8 @@
1
1
  // Generated by OpenXiangda 2.0 Native compiler. Do not edit by hand.
2
- export const compilerContractVersion = "native-2" as const;
2
+ export const compilerContractVersion = "native-4" as const;
3
3
  export const appCode = "openxiangda-application" as const;
4
4
  export const appName = "OpenXiangda 应用" as const;
5
+ export const appPerspectives = [] as const;
5
6
  export const resourceCodes = [] as const;
6
7
  export const resourceSurfaces = {} as const;
7
8
  export const resourceDefinitions = {} as const;
@@ -15,7 +16,132 @@ export const capabilities = [
15
16
  "app:super-admin:manage"
16
17
  ] as const;
17
18
  export const appOperations = {} as const;
18
- export const appRoutes = {} as const;
19
+ export const appRoutes = {
20
+ applicationHome: {
21
+ "code": "application-home",
22
+ "path": "/home",
23
+ "label": "应用首页",
24
+ "surface": "user",
25
+ "tabPersistence": "session",
26
+ "keepAlive": "none"
27
+ },
28
+ applicationHomeMobile: {
29
+ "code": "application-home-mobile",
30
+ "path": "/m/home",
31
+ "label": "移动首页",
32
+ "surface": "user",
33
+ "tabPersistence": "session",
34
+ "keepAlive": "none"
35
+ }
36
+ } as const;
37
+ export const authenticationMethods = [
38
+ {
39
+ "code": "password",
40
+ "type": "password",
41
+ "label": "账号密码登录",
42
+ "presentation": "primary",
43
+ "required": true
44
+ }
45
+ ] as const;
46
+ export const authenticationSurfaces = {
47
+ applicationLogin: {
48
+ "device": "desktop",
49
+ "routeCode": "application-login",
50
+ "path": "/login",
51
+ "defaultRouteCode": "application-home"
52
+ },
53
+ applicationLoginMobile: {
54
+ "device": "mobile",
55
+ "routeCode": "application-login-mobile",
56
+ "path": "/m/login",
57
+ "defaultRouteCode": "application-home-mobile"
58
+ }
59
+ } as const;
60
+ export const applicationAuthentication = {
61
+ "accountMode": "existing-platform-users-only",
62
+ "registration": {
63
+ "mode": "reject"
64
+ },
65
+ "methods": [
66
+ {
67
+ "code": "password",
68
+ "type": "password",
69
+ "label": "账号密码登录",
70
+ "presentation": "primary",
71
+ "required": true
72
+ }
73
+ ],
74
+ "surfaces": {
75
+ "desktop": {
76
+ "routeCode": "application-login",
77
+ "path": "/login",
78
+ "defaultRouteCode": "application-home"
79
+ },
80
+ "mobile": {
81
+ "routeCode": "application-login-mobile",
82
+ "path": "/m/login",
83
+ "defaultRouteCode": "application-home-mobile"
84
+ }
85
+ }
86
+ } as const;
87
+ export const anonymousPublicAccess = undefined;
88
+ export const platformAuthManifest = {
89
+ "schemaVersion": "openxiangda.platform-auth-manifest/v2",
90
+ "appCode": "openxiangda-application",
91
+ "protectedUserRouteCount": 2,
92
+ "methods": [
93
+ {
94
+ "code": "password",
95
+ "type": "password",
96
+ "label": "账号密码登录",
97
+ "presentation": "primary",
98
+ "required": true
99
+ }
100
+ ],
101
+ "surfaces": [
102
+ {
103
+ "device": "desktop",
104
+ "routeCode": "application-login",
105
+ "path": "/login",
106
+ "defaultRouteCode": "application-home"
107
+ },
108
+ {
109
+ "device": "mobile",
110
+ "routeCode": "application-login-mobile",
111
+ "path": "/m/login",
112
+ "defaultRouteCode": "application-home-mobile"
113
+ }
114
+ ]
115
+ } as const;
116
+ export const adminAccess = undefined;
117
+ export const routeManifest = {
118
+ "schemaVersion": "openxiangda.application-route-manifest/v3",
119
+ "appCode": "openxiangda-application",
120
+ "devicePolicy": {
121
+ "kind": "viewport-family",
122
+ "mobileMaxWidthPx": 900,
123
+ "desktopMinWidthPx": 901
124
+ },
125
+ "rootEntry": {
126
+ "code": "application-home",
127
+ "desktop": "/home",
128
+ "mobile": "/m/home"
129
+ },
130
+ "authentication": {
131
+ "desktop": {
132
+ "routeCode": "application-login",
133
+ "path": "/login"
134
+ },
135
+ "mobile": {
136
+ "routeCode": "application-login-mobile",
137
+ "path": "/m/login"
138
+ }
139
+ },
140
+ "routes": [],
141
+ "digest": "2c01252ac4415de49a2fe58bf21df94a77f8c235d739ec7a6af583fc695ba0cf"
142
+ } as const;
143
+ export const adminPages = [] as const;
144
+ export const adminNavigation = [] as const;
19
145
  export const eventTypes = [] as const;
20
146
  export const eventSubscriptionCodes = [] as const;
21
147
  export const eventHandlerManifest = {
@@ -26,15 +152,30 @@ export const eventHandlerManifest = {
26
152
  export const eventHandlers = {} as const;
27
153
  export const eventSchemas = [] as const;
28
154
  export const workflowCodes = [] as const;
155
+ export const workflowDefinitions = [] as const;
156
+ export const notificationTemplateCodes = {
157
+ applicationInformational: "application.informational.standard",
158
+ } as const;
29
159
 
30
160
  export type ResourceCode = (typeof resourceCodes)[number];
31
161
  export type Capability = (typeof capabilities)[number];
32
162
  export type AppOperation = (typeof appOperations)[keyof typeof appOperations];
33
163
  export type AppRoute = (typeof appRoutes)[keyof typeof appRoutes];
164
+ export type AppRouteCode = AppRoute["code"];
165
+ export type ApplicationAuthenticationMethod = (typeof authenticationMethods)[number];
166
+ export type ApplicationAuthenticationSurface = (typeof authenticationSurfaces)[keyof typeof authenticationSurfaces];
167
+ export type ApplicationAuthenticationSurfaceCode = ApplicationAuthenticationSurface["routeCode"];
168
+ export type AnonymousPublicAccess = typeof anonymousPublicAccess;
169
+ export type AdminAccess = typeof adminAccess;
170
+ export type RouteManifest = typeof routeManifest;
171
+ export type AdminPage = (typeof adminPages)[number];
172
+ export type AdminNavigationGroup = (typeof adminNavigation)[number];
173
+ export type AppPerspective = (typeof appPerspectives)[number];
34
174
  export type EventType = (typeof eventTypes)[number];
35
175
  export type EventSubscriptionCode = (typeof eventSubscriptionCodes)[number];
36
176
  export type EventTypesForSubscription<TCode extends EventSubscriptionCode> = never;
37
177
  export type WorkflowCode = (typeof workflowCodes)[number];
178
+ export type NotificationTemplateCode = (typeof notificationTemplateCodes)[keyof typeof notificationTemplateCodes];
38
179
 
39
180
  export interface OpenXiangdaCloudEvent<TType extends EventType = EventType, TData extends Record<string, unknown> = Record<string, unknown>> {
40
181
  specversion: "1.0";
@@ -1,20 +0,0 @@
1
- FROM node:22-alpine AS build
2
- WORKDIR /workspace
3
- RUN corepack enable
4
- COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json ./
5
- COPY apps/server/package.json apps/server/package.json
6
- COPY packages/contracts/package.json packages/contracts/package.json
7
- RUN node -e "const fs=require('node:fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); delete p.devDependencies; fs.writeFileSync('package.json', JSON.stringify(p));"
8
- RUN pnpm --filter @app/server --filter @app/contracts install --no-frozen-lockfile --ignore-scripts
9
- COPY apps/server apps/server
10
- COPY packages/contracts packages/contracts
11
- RUN pnpm --filter @app/server... build
12
- RUN pnpm --filter @app/server deploy --prod --legacy --ignore-scripts /output
13
-
14
- FROM node:22-alpine AS runtime
15
- ENV NODE_ENV=production
16
- WORKDIR /app
17
- COPY --from=build --chown=1000:1000 /output /app
18
- USER 1000:1000
19
- EXPOSE 3000
20
- CMD ["node", "dist/main.js"]