openxiangda-cli 2.0.0-alpha.16 → 2.0.0-alpha.162

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 (276) hide show
  1. package/README.md +51 -15
  2. package/bin/run.js +79 -1
  3. package/dist/base.d.ts +59 -5
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +213 -10
  6. package/dist/base.js.map +1 -1
  7. package/dist/commands/accept.d.ts +28 -0
  8. package/dist/commands/accept.d.ts.map +1 -0
  9. package/dist/commands/accept.js +21 -0
  10. package/dist/commands/accept.js.map +1 -0
  11. package/dist/commands/cancel.d.ts +19 -1
  12. package/dist/commands/cancel.d.ts.map +1 -1
  13. package/dist/commands/cancel.js +1 -1
  14. package/dist/commands/cancel.js.map +1 -1
  15. package/dist/commands/check.d.ts +22 -1
  16. package/dist/commands/check.d.ts.map +1 -1
  17. package/dist/commands/check.js +17 -4
  18. package/dist/commands/check.js.map +1 -1
  19. package/dist/commands/create.d.ts +39 -0
  20. package/dist/commands/create.d.ts.map +1 -0
  21. package/dist/commands/create.js +457 -0
  22. package/dist/commands/create.js.map +1 -0
  23. package/dist/commands/deploy.d.ts +26 -8
  24. package/dist/commands/deploy.d.ts.map +1 -1
  25. package/dist/commands/deploy.js +31 -17
  26. package/dist/commands/deploy.js.map +1 -1
  27. package/dist/commands/dev.d.ts +23 -1
  28. package/dist/commands/dev.d.ts.map +1 -1
  29. package/dist/commands/dev.js +28 -4
  30. package/dist/commands/dev.js.map +1 -1
  31. package/dist/commands/login.d.ts +29 -0
  32. package/dist/commands/login.d.ts.map +1 -0
  33. package/dist/commands/{auth/login.js → login.js} +27 -9
  34. package/dist/commands/login.js.map +1 -0
  35. package/dist/commands/logs.d.ts +22 -2
  36. package/dist/commands/logs.d.ts.map +1 -1
  37. package/dist/commands/logs.js +9 -5
  38. package/dist/commands/logs.js.map +1 -1
  39. package/dist/commands/retry.d.ts +19 -1
  40. package/dist/commands/retry.d.ts.map +1 -1
  41. package/dist/commands/retry.js +5 -2
  42. package/dist/commands/retry.js.map +1 -1
  43. package/dist/commands/rollback.d.ts +23 -4
  44. package/dist/commands/rollback.d.ts.map +1 -1
  45. package/dist/commands/rollback.js +18 -5
  46. package/dist/commands/rollback.js.map +1 -1
  47. package/dist/commands/skill.d.ts +31 -0
  48. package/dist/commands/skill.d.ts.map +1 -0
  49. package/dist/commands/skill.js +43 -0
  50. package/dist/commands/skill.js.map +1 -0
  51. package/dist/commands/spec.d.ts +39 -0
  52. package/dist/commands/spec.d.ts.map +1 -0
  53. package/dist/commands/spec.js +117 -0
  54. package/dist/commands/spec.js.map +1 -0
  55. package/dist/commands/start.d.ts +28 -0
  56. package/dist/commands/start.d.ts.map +1 -0
  57. package/dist/commands/start.js +18 -0
  58. package/dist/commands/start.js.map +1 -0
  59. package/dist/commands/status.d.ts +19 -1
  60. package/dist/commands/status.d.ts.map +1 -1
  61. package/dist/commands/status.js +1 -1
  62. package/dist/commands/status.js.map +1 -1
  63. package/dist/commands/stop.d.ts +28 -0
  64. package/dist/commands/stop.d.ts.map +1 -0
  65. package/dist/commands/stop.js +18 -0
  66. package/dist/commands/stop.js.map +1 -0
  67. package/dist/create-workspace.d.ts +67 -0
  68. package/dist/create-workspace.d.ts.map +1 -0
  69. package/dist/create-workspace.js +631 -0
  70. package/dist/create-workspace.js.map +1 -0
  71. package/dist/studio-events.d.ts +9 -0
  72. package/dist/studio-events.d.ts.map +1 -0
  73. package/dist/studio-events.js +30 -0
  74. package/dist/studio-events.js.map +1 -0
  75. package/dist/toolchain-capsule.d.ts +4 -0
  76. package/dist/toolchain-capsule.d.ts.map +1 -0
  77. package/dist/toolchain-capsule.js +116 -0
  78. package/dist/toolchain-capsule.js.map +1 -0
  79. package/package.json +16 -9
  80. package/template/.dockerignore +23 -0
  81. package/template/.env.example +3 -0
  82. package/template/AGENTS.md +47 -0
  83. package/template/README.md +65 -0
  84. package/template/_gitignore +14 -0
  85. package/template/apps/server/Dockerfile +20 -0
  86. package/template/apps/server/package.json +27 -0
  87. package/template/apps/server/src/app.module.ts +23 -0
  88. package/template/apps/server/src/main.ts +5 -0
  89. package/template/apps/server/test/smoke.test.ts +17 -0
  90. package/template/apps/server/tsconfig.json +9 -0
  91. package/template/apps/web/data-api-live.e2e.html +15 -0
  92. package/template/apps/web/e2e/client-fixture.ts +5 -0
  93. package/template/apps/web/e2e/data-api-live-fixture.tsx +162 -0
  94. package/template/apps/web/e2e/data-api-live.spec.ts +144 -0
  95. package/template/apps/web/e2e/field-protocol-fixture.tsx +350 -0
  96. package/template/apps/web/e2e/field-protocol.spec.ts +195 -0
  97. package/template/apps/web/e2e/resource-experience-fixture.tsx +446 -0
  98. package/template/apps/web/e2e/resources.spec.ts +638 -0
  99. package/template/apps/web/e2e/theme-contract-fixture.css +15 -0
  100. package/template/apps/web/e2e/workflow-experience-fixture.tsx +247 -0
  101. package/template/apps/web/e2e/workflow.spec.ts +1066 -0
  102. package/template/apps/web/field-protocol.e2e.html +15 -0
  103. package/template/apps/web/index.html +12 -0
  104. package/template/apps/web/package.json +34 -0
  105. package/template/apps/web/playwright.config.ts +47 -0
  106. package/template/apps/web/resource-experience.e2e.html +15 -0
  107. package/template/apps/web/scripts/check.mjs +43 -0
  108. package/template/apps/web/scripts/verify-build.mjs +6 -0
  109. package/template/apps/web/src/main.tsx +65 -0
  110. package/template/apps/web/src/user-end/tailwind.css +17 -0
  111. package/template/apps/web/test/contracts.test.ts +161 -0
  112. package/template/apps/web/test/loopback.test.ts +68 -0
  113. package/template/apps/web/tsconfig.json +12 -0
  114. package/template/apps/web/vite.config.ts +41 -0
  115. package/template/apps/web/workflow-experience.e2e.html +15 -0
  116. package/template/appspec/app.md +36 -0
  117. package/template/openxiangda.config.ts +77 -0
  118. package/template/package.json +29 -0
  119. package/template/packages/contracts/package.json +23 -0
  120. package/template/packages/contracts/src/generated.ts +265 -0
  121. package/template/packages/contracts/src/index.ts +1 -0
  122. package/template/packages/contracts/tsconfig.json +9 -0
  123. package/template/pnpm-workspace.yaml +7 -0
  124. package/template/scripts/verify-template-budget.mjs +49 -0
  125. package/template/tsconfig.base.json +13 -0
  126. package/dist/commands/app/create.d.ts +0 -15
  127. package/dist/commands/app/create.d.ts.map +0 -1
  128. package/dist/commands/app/create.js +0 -42
  129. package/dist/commands/app/create.js.map +0 -1
  130. package/dist/commands/app/info.d.ts +0 -9
  131. package/dist/commands/app/info.d.ts.map +0 -1
  132. package/dist/commands/app/info.js +0 -10
  133. package/dist/commands/app/info.js.map +0 -1
  134. package/dist/commands/app/link.d.ts +0 -10
  135. package/dist/commands/app/link.d.ts.map +0 -1
  136. package/dist/commands/app/link.js +0 -16
  137. package/dist/commands/app/link.js.map +0 -1
  138. package/dist/commands/app/provision.d.ts +0 -9
  139. package/dist/commands/app/provision.d.ts.map +0 -1
  140. package/dist/commands/app/provision.js +0 -10
  141. package/dist/commands/app/provision.js.map +0 -1
  142. package/dist/commands/auth/login.d.ts +0 -11
  143. package/dist/commands/auth/login.d.ts.map +0 -1
  144. package/dist/commands/auth/login.js.map +0 -1
  145. package/dist/commands/auth/logout.d.ts +0 -6
  146. package/dist/commands/auth/logout.d.ts.map +0 -1
  147. package/dist/commands/auth/logout.js +0 -30
  148. package/dist/commands/auth/logout.js.map +0 -1
  149. package/dist/commands/auth/status.d.ts +0 -6
  150. package/dist/commands/auth/status.d.ts.map +0 -1
  151. package/dist/commands/auth/status.js +0 -60
  152. package/dist/commands/auth/status.js.map +0 -1
  153. package/dist/commands/build.d.ts +0 -11
  154. package/dist/commands/build.d.ts.map +0 -1
  155. package/dist/commands/build.js +0 -23
  156. package/dist/commands/build.js.map +0 -1
  157. package/dist/commands/doctor.d.ts +0 -9
  158. package/dist/commands/doctor.d.ts.map +0 -1
  159. package/dist/commands/doctor.js +0 -7
  160. package/dist/commands/doctor.js.map +0 -1
  161. package/dist/commands/event/delivery/list.d.ts +0 -10
  162. package/dist/commands/event/delivery/list.d.ts.map +0 -1
  163. package/dist/commands/event/delivery/list.js +0 -14
  164. package/dist/commands/event/delivery/list.js.map +0 -1
  165. package/dist/commands/event/delivery/replay.d.ts +0 -13
  166. package/dist/commands/event/delivery/replay.d.ts.map +0 -1
  167. package/dist/commands/event/delivery/replay.js +0 -16
  168. package/dist/commands/event/delivery/replay.js.map +0 -1
  169. package/dist/commands/event/subscription/list.d.ts +0 -9
  170. package/dist/commands/event/subscription/list.d.ts.map +0 -1
  171. package/dist/commands/event/subscription/list.js +0 -10
  172. package/dist/commands/event/subscription/list.js.map +0 -1
  173. package/dist/commands/event/subscription/rotate-secret.d.ts +0 -15
  174. package/dist/commands/event/subscription/rotate-secret.d.ts.map +0 -1
  175. package/dist/commands/event/subscription/rotate-secret.js +0 -33
  176. package/dist/commands/event/subscription/rotate-secret.js.map +0 -1
  177. package/dist/commands/event/subscription/status.d.ts +0 -14
  178. package/dist/commands/event/subscription/status.d.ts.map +0 -1
  179. package/dist/commands/event/subscription/status.js +0 -19
  180. package/dist/commands/event/subscription/status.js.map +0 -1
  181. package/dist/commands/event/timer/list.d.ts +0 -9
  182. package/dist/commands/event/timer/list.d.ts.map +0 -1
  183. package/dist/commands/event/timer/list.js +0 -10
  184. package/dist/commands/event/timer/list.js.map +0 -1
  185. package/dist/commands/event/timer/status.d.ts +0 -14
  186. package/dist/commands/event/timer/status.d.ts.map +0 -1
  187. package/dist/commands/event/timer/status.js +0 -19
  188. package/dist/commands/event/timer/status.js.map +0 -1
  189. package/dist/commands/generate.d.ts +0 -10
  190. package/dist/commands/generate.d.ts.map +0 -1
  191. package/dist/commands/generate.js +0 -8
  192. package/dist/commands/generate.js.map +0 -1
  193. package/dist/commands/oauth/audit/list.d.ts +0 -12
  194. package/dist/commands/oauth/audit/list.d.ts.map +0 -1
  195. package/dist/commands/oauth/audit/list.js +0 -20
  196. package/dist/commands/oauth/audit/list.js.map +0 -1
  197. package/dist/commands/oauth/client/create.d.ts +0 -13
  198. package/dist/commands/oauth/client/create.d.ts.map +0 -1
  199. package/dist/commands/oauth/client/create.js +0 -34
  200. package/dist/commands/oauth/client/create.js.map +0 -1
  201. package/dist/commands/oauth/client/list.d.ts +0 -9
  202. package/dist/commands/oauth/client/list.d.ts.map +0 -1
  203. package/dist/commands/oauth/client/list.js +0 -10
  204. package/dist/commands/oauth/client/list.js.map +0 -1
  205. package/dist/commands/oauth/client/revoke.d.ts +0 -12
  206. package/dist/commands/oauth/client/revoke.d.ts.map +0 -1
  207. package/dist/commands/oauth/client/revoke.js +0 -12
  208. package/dist/commands/oauth/client/revoke.js.map +0 -1
  209. package/dist/commands/oauth/client/rotate.d.ts +0 -13
  210. package/dist/commands/oauth/client/rotate.d.ts.map +0 -1
  211. package/dist/commands/oauth/client/rotate.js +0 -20
  212. package/dist/commands/oauth/client/rotate.js.map +0 -1
  213. package/dist/commands/oauth/client/update.d.ts +0 -15
  214. package/dist/commands/oauth/client/update.d.ts.map +0 -1
  215. package/dist/commands/oauth/client/update.js +0 -23
  216. package/dist/commands/oauth/client/update.js.map +0 -1
  217. package/dist/commands/oauth/runtime/rotate.d.ts +0 -12
  218. package/dist/commands/oauth/runtime/rotate.d.ts.map +0 -1
  219. package/dist/commands/oauth/runtime/rotate.js +0 -31
  220. package/dist/commands/oauth/runtime/rotate.js.map +0 -1
  221. package/dist/commands/oauth/runtime/status.d.ts +0 -10
  222. package/dist/commands/oauth/runtime/status.d.ts.map +0 -1
  223. package/dist/commands/oauth/runtime/status.js +0 -17
  224. package/dist/commands/oauth/runtime/status.js.map +0 -1
  225. package/dist/commands/promote.d.ts +0 -13
  226. package/dist/commands/promote.d.ts.map +0 -1
  227. package/dist/commands/promote.js +0 -9
  228. package/dist/commands/promote.js.map +0 -1
  229. package/dist/commands/secret/audit/list.d.ts +0 -14
  230. package/dist/commands/secret/audit/list.d.ts.map +0 -1
  231. package/dist/commands/secret/audit/list.js +0 -16
  232. package/dist/commands/secret/audit/list.js.map +0 -1
  233. package/dist/commands/secret/create.d.ts +0 -18
  234. package/dist/commands/secret/create.d.ts.map +0 -1
  235. package/dist/commands/secret/create.js +0 -33
  236. package/dist/commands/secret/create.js.map +0 -1
  237. package/dist/commands/secret/delete.d.ts +0 -15
  238. package/dist/commands/secret/delete.d.ts.map +0 -1
  239. package/dist/commands/secret/delete.js +0 -20
  240. package/dist/commands/secret/delete.js.map +0 -1
  241. package/dist/commands/secret/list.d.ts +0 -10
  242. package/dist/commands/secret/list.d.ts.map +0 -1
  243. package/dist/commands/secret/list.js +0 -14
  244. package/dist/commands/secret/list.js.map +0 -1
  245. package/dist/commands/secret/rotate.d.ts +0 -18
  246. package/dist/commands/secret/rotate.d.ts.map +0 -1
  247. package/dist/commands/secret/rotate.js +0 -30
  248. package/dist/commands/secret/rotate.js.map +0 -1
  249. package/dist/commands/secret/update.d.ts +0 -17
  250. package/dist/commands/secret/update.d.ts.map +0 -1
  251. package/dist/commands/secret/update.js +0 -37
  252. package/dist/commands/secret/update.js.map +0 -1
  253. package/dist/commands/skill/install.d.ts +0 -11
  254. package/dist/commands/skill/install.d.ts.map +0 -1
  255. package/dist/commands/skill/install.js +0 -52
  256. package/dist/commands/skill/install.js.map +0 -1
  257. package/dist/commands/skill/validate.d.ts +0 -9
  258. package/dist/commands/skill/validate.d.ts.map +0 -1
  259. package/dist/commands/skill/validate.js +0 -32
  260. package/dist/commands/skill/validate.js.map +0 -1
  261. package/dist/commands/test.d.ts +0 -9
  262. package/dist/commands/test.d.ts.map +0 -1
  263. package/dist/commands/test.js +0 -7
  264. package/dist/commands/test.js.map +0 -1
  265. package/dist/commands/workflow/provider/list.d.ts +0 -10
  266. package/dist/commands/workflow/provider/list.d.ts.map +0 -1
  267. package/dist/commands/workflow/provider/list.js +0 -14
  268. package/dist/commands/workflow/provider/list.js.map +0 -1
  269. package/dist/commands/workflow/provider/rotate.d.ts +0 -15
  270. package/dist/commands/workflow/provider/rotate.d.ts.map +0 -1
  271. package/dist/commands/workflow/provider/rotate.js +0 -24
  272. package/dist/commands/workflow/provider/rotate.js.map +0 -1
  273. package/dist/secret-value.d.ts +0 -5
  274. package/dist/secret-value.d.ts.map +0 -1
  275. package/dist/secret-value.js +0 -15
  276. package/dist/secret-value.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="openxiangda-runtime-base" content="/" />
7
+ <meta name="openxiangda-app-code" content="field-protocol-fixture" />
8
+ <meta name="openxiangda-environment" content="preproduction" />
9
+ <title>Field protocol acceptance</title>
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/e2e/field-protocol-fixture.tsx"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>OpenXiangda 应用</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@app/web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "check": "tsc -p tsconfig.json --noEmit && node scripts/check.mjs",
9
+ "test": "tsx --test test/*.test.ts",
10
+ "test:e2e": "playwright test",
11
+ "build": "tsc -p tsconfig.json --noEmit && vite build && node scripts/verify-build.mjs"
12
+ },
13
+ "dependencies": {
14
+ "@ant-design/icons": "6.2.3",
15
+ "@refinedev/core": "5.0.12",
16
+ "antd": "6.4.3",
17
+ "openxiangda": "2.0.0-alpha.85",
18
+ "react": "19.2.8",
19
+ "react-dom": "19.2.8",
20
+ "react-router-dom": "7.8.2"
21
+ },
22
+ "devDependencies": {
23
+ "@playwright/test": "1.62.1",
24
+ "@tailwindcss/vite": "4.3.3",
25
+ "@types/node": "24.13.3",
26
+ "@types/react": "19.2.14",
27
+ "@types/react-dom": "19.2.3",
28
+ "@vitejs/plugin-react": "4.7.0",
29
+ "tailwindcss": "4.3.3",
30
+ "tsx": "4.23.12",
31
+ "typescript": "5.9.3",
32
+ "vite": "7.1.3"
33
+ }
34
+ }
@@ -0,0 +1,47 @@
1
+ import { defineConfig } from '@playwright/test';
2
+
3
+ const e2ePort = resolveE2ePort();
4
+ const baseURL =
5
+ process.env.OPENXIANGDA_E2E_BASE_URL || `http://127.0.0.1:${e2ePort}`;
6
+
7
+ export default defineConfig({
8
+ testDir: './e2e',
9
+ // The development server owns one mutable dependency-optimizer generation.
10
+ // Serial browser entrypoints prevent a second HTML graph from invalidating
11
+ // an in-flight transformed module during release acceptance.
12
+ workers: 1,
13
+ use: { baseURL, trace: 'retain-on-failure' },
14
+ webServer: process.env.OPENXIANGDA_E2E_BASE_URL
15
+ ? undefined
16
+ : {
17
+ command: 'pnpm dev',
18
+ url: baseURL,
19
+ reuseExistingServer: false,
20
+ env: {
21
+ OPENXIANGDA_WEB_PORT: String(e2ePort),
22
+ OPENXIANGDA_ENVIRONMENT_KEY: 'production',
23
+ OPENXIANGDA_DEV_PROXY:
24
+ process.env.OPENXIANGDA_E2E_PLATFORM_URL ||
25
+ 'http://127.0.0.1:7001',
26
+ },
27
+ },
28
+ });
29
+
30
+ function resolveE2ePort() {
31
+ const configured = process.env.OPENXIANGDA_E2E_PORT;
32
+ if (configured !== undefined) {
33
+ const port = Number(configured);
34
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
35
+ throw new Error(
36
+ 'OPENXIANGDA_E2E_PORT must be an integer between 1024 and 65535'
37
+ );
38
+ }
39
+ return port;
40
+ }
41
+
42
+ let hash = 2166136261;
43
+ for (const character of process.cwd()) {
44
+ hash = Math.imul(hash ^ character.charCodeAt(0), 16777619);
45
+ }
46
+ return 30_000 + ((hash >>> 0) % 30_000);
47
+ }
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="openxiangda-runtime-base" content="/" />
7
+ <meta name="openxiangda-app-code" content="openxiangda-application" />
8
+ <meta name="openxiangda-environment" content="preproduction" />
9
+ <title>Resource experience acceptance</title>
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/e2e/resource-experience-fixture.tsx"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,43 @@
1
+ import { readFileSync, readdirSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+
4
+ const root = resolve(import.meta.dirname, '..');
5
+ const files = [];
6
+ function visit(directory) {
7
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
8
+ const path = join(directory, entry.name);
9
+ if (entry.isDirectory() && !['.umi', '.umi-production', '.turbopack'].includes(entry.name)) visit(path);
10
+ else if (/\.(?:ts|tsx)$/.test(entry.name)) files.push(path);
11
+ }
12
+ }
13
+ visit(join(root, 'src'));
14
+ const lines = files.reduce((total, file) => total + readFileSync(file, 'utf8').split(/\r?\n/).length, 0);
15
+ // The shared Surface renderer is deliberately counted as application source;
16
+ // keep its small budget explicit instead of hiding it in generated output.
17
+ // Field Kit families are separate modules so a semantic field does not add a
18
+ // branch to one universal renderer. Keep explicit ceilings while allowing the
19
+ // complete 30-field standard kit to remain decomposed.
20
+ if (files.length > 36) throw new Error(`WEB_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>36`);
21
+ if (lines > 10_000) throw new Error(`WEB_BUSINESS_LOC_BUDGET_EXCEEDED:${lines}>10000`);
22
+ const source = files.map((file) => readFileSync(file, 'utf8')).join('\n');
23
+ for (const marker of [
24
+ '@umijs/',
25
+ 'openxiangda-' + 'admin',
26
+ 'openxiangda-' + 'user',
27
+ 'instrument_query',
28
+ 'instrument_save',
29
+ 'function.invoke',
30
+ 'authorization' + '-mode',
31
+ 'focused' + '_role',
32
+ '/native/current' + '-user',
33
+ 'college-' + 'am',
34
+ 'user-' + 'wang',
35
+ 'dept-' + 'am-test',
36
+ ]) {
37
+ if (source.includes(marker)) throw new Error(`WEB_FORBIDDEN_MARKER:${marker}`);
38
+ }
39
+ const playwright = readFileSync(join(root, 'playwright.config.ts'), 'utf8');
40
+ if (!playwright.includes('reuseExistingServer: false')) {
41
+ throw new Error('WEB_E2E_MUST_NOT_REUSE_UNKNOWN_SERVER');
42
+ }
43
+ process.stdout.write(`Verified Vite/Refine web source: ${files.length} files, ${lines} LOC.\n`);
@@ -0,0 +1,6 @@
1
+ import { verifyOpenXiangdaWebBuild } from 'openxiangda/testing';
2
+
3
+ const result = verifyOpenXiangdaWebBuild(new URL('../dist', import.meta.url));
4
+ process.stdout.write(
5
+ `Verified Vite build: ${result.totalBytes} bytes, ${result.totalJavaScriptGzipBytes} total gzip bytes, ${result.initialJavaScriptGzipBytes} initial gzip bytes.\n`
6
+ );
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import {
4
+ defineApplicationContributions,
5
+ DefaultDesktopApplicationLoginSurface,
6
+ DefaultMobileApplicationLoginSurface,
7
+ OpenXiangdaApplication,
8
+ type ApplicationLoginSurfaceProps,
9
+ } from 'openxiangda/react';
10
+ import {
11
+ appCode,
12
+ appName,
13
+ appPerspectives,
14
+ appRoutes,
15
+ routeManifest,
16
+ authenticationSurfaces,
17
+ anonymousPublicAccess,
18
+ adminNavigation,
19
+ adminAccess,
20
+ adminPages,
21
+ resourceDefinitions,
22
+ workflowDefinitions,
23
+ } from '../../../packages/contracts/src/generated.js';
24
+ import './user-end/tailwind.css';
25
+ import 'openxiangda/react/styles.css';
26
+
27
+ const ApplicationHome = () => <main>OpenXiangda 应用</main>;
28
+ const ApplicationHomeMobile = () => <main>OpenXiangda 移动应用</main>;
29
+ const BrandedDesktopLogin = (props: ApplicationLoginSurfaceProps) => (
30
+ <DefaultDesktopApplicationLoginSurface {...props} />
31
+ );
32
+ const BrandedMobileLogin = (props: ApplicationLoginSurfaceProps) => (
33
+ <DefaultMobileApplicationLoginSurface {...props} />
34
+ );
35
+
36
+ const applicationContributions = defineApplicationContributions(
37
+ { routes: appRoutes, authenticationSurfaces },
38
+ {
39
+ pages: {
40
+ applicationHome: ApplicationHome,
41
+ applicationHomeMobile: ApplicationHomeMobile,
42
+ },
43
+ authentication: {
44
+ applicationLogin: BrandedDesktopLogin,
45
+ applicationLoginMobile: BrandedMobileLogin,
46
+ },
47
+ },
48
+ );
49
+ ReactDOM.createRoot(document.getElementById('root')!).render(
50
+ <React.StrictMode>
51
+ <OpenXiangdaApplication
52
+ appCode={appCode}
53
+ appName={appName}
54
+ adminAccess={adminAccess}
55
+ adminNavigation={adminNavigation}
56
+ adminPages={adminPages}
57
+ routeManifest={routeManifest}
58
+ contributions={applicationContributions}
59
+ publicAccess={anonymousPublicAccess}
60
+ perspectives={appPerspectives}
61
+ resourceDefinitions={resourceDefinitions}
62
+ workflows={workflowDefinitions}
63
+ />
64
+ </React.StrictMode>,
65
+ );
@@ -0,0 +1,17 @@
1
+ /* User-end Tailwind foundation. Keep its reset and tokens separate from admin. */
2
+ @import "tailwindcss/theme.css";
3
+ @import "tailwindcss/utilities.css";
4
+
5
+ @source "../";
6
+
7
+ @theme {
8
+ --font-sans: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
9
+ --font-display: Inter, "PingFang SC", "Microsoft YaHei", sans-serif;
10
+ --color-user-brand-500: oklch(58.5% 0.205 256);
11
+ --color-user-brand-600: oklch(51.5% 0.224 256);
12
+ --color-user-surface: oklch(99% 0.002 256);
13
+ --color-user-canvas: oklch(97.5% 0.006 256);
14
+ --color-user-ink: oklch(25% 0.025 256);
15
+ --color-user-muted: oklch(50% 0.025 256);
16
+ --radius-user-card: 1rem;
17
+ }
@@ -0,0 +1,161 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import test from 'node:test';
4
+ import { readRuntimeMount, resolveApplicationBasename } from 'openxiangda/core';
5
+ import {
6
+ notificationTemplateCodes,
7
+ platformAuthManifest,
8
+ resourceCodes,
9
+ resourceDefinitions,
10
+ } from '../../../packages/contracts/src/generated.js';
11
+
12
+ test('clean template contains only application declarations and one React entry', () => {
13
+ const config = readFileSync(
14
+ new URL('../../../openxiangda.config.ts', import.meta.url),
15
+ 'utf8'
16
+ );
17
+ const source = readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8');
18
+ const productionSources = readdirSync(new URL('../src', import.meta.url), {
19
+ withFileTypes: true,
20
+ })
21
+ .filter(entry => entry.isFile())
22
+ .map(entry => entry.name)
23
+ .sort();
24
+ assert.deepEqual(productionSources, ['main.tsx']);
25
+ assert.deepEqual(Object.keys(resourceDefinitions).sort(), [...resourceCodes].sort());
26
+ assert.equal(
27
+ notificationTemplateCodes.applicationInformational,
28
+ 'application.informational.standard'
29
+ );
30
+ assert.equal(platformAuthManifest?.protectedUserRouteCount, 2);
31
+ assert.equal(platformAuthManifest?.surfaces.length, 2);
32
+ assert.match(config, /data:\s*\{\s*resources:/);
33
+ assert.match(source, /OpenXiangdaApplication/);
34
+ assert.match(source, /routes: appRoutes, authenticationSurfaces/);
35
+ assert.match(source, /DefaultDesktopApplicationLoginSurface/);
36
+ assert.match(source, /DefaultMobileApplicationLoginSurface/);
37
+ assert.match(source, /contributions=\{applicationContributions\}/);
38
+ assert.match(source, /publicAccess=\{anonymousPublicAccess\}/);
39
+ assert.match(source, /from 'openxiangda\/react'/);
40
+ assert.match(source, /openxiangda\/react\/styles\.css/);
41
+ const legacyMarkers = [
42
+ 'instruments',
43
+ 'colleges',
44
+ '仪器' + '资源管理',
45
+ '学院' + '字典',
46
+ 'HGY',
47
+ 'Instrument',
48
+ ];
49
+ assert.doesNotMatch(`${config}\n${source}`, new RegExp(legacyMarkers.join('|')));
50
+ });
51
+
52
+ test('application manifests expose one direct OpenXiangda dependency', () => {
53
+ for (const relative of [
54
+ '../../../package.json',
55
+ '../package.json',
56
+ '../../server/package.json',
57
+ ]) {
58
+ const manifest = JSON.parse(
59
+ readFileSync(new URL(relative, import.meta.url), 'utf8')
60
+ ) as Record<string, Record<string, string>>;
61
+ const dependencies = Object.entries({
62
+ ...(manifest.dependencies || {}),
63
+ ...(manifest.devDependencies || {}),
64
+ ...(manifest.peerDependencies || {}),
65
+ }).filter(([name]) =>
66
+ name === 'openxiangda' || name.startsWith('openxiangda-')
67
+ );
68
+ assert.deepEqual(
69
+ dependencies.map(([name]) => name),
70
+ dependencies.length ? ['openxiangda'] : []
71
+ );
72
+ }
73
+ });
74
+
75
+ test('runtime metadata stays mount-scoped', () => {
76
+ const mount = readRuntimeMount(
77
+ name =>
78
+ ({
79
+ 'openxiangda-runtime-base': '/view/example-app/',
80
+ 'openxiangda-app-code': 'example-app',
81
+ 'openxiangda-environment': 'preproduction',
82
+ })[name]
83
+ );
84
+ assert.equal(resolveApplicationBasename(mount), '/view/example-app');
85
+ });
86
+
87
+ test('resource E2E harness owns the standard export download response', () => {
88
+ const source = readFileSync(
89
+ new URL('../e2e/resources.spec.ts', import.meta.url),
90
+ 'utf8'
91
+ );
92
+ assert.match(source, /\/native\\\/data\\\/\(\[\^\/\]\+\)\\\/export\$/);
93
+ assert.match(source, /content-disposition/);
94
+ assert.match(source, /text\/csv/);
95
+ });
96
+
97
+ test('published E2E harnesses do not write evidence into the platform source repository', () => {
98
+ const workflowSource = readFileSync(
99
+ new URL('../e2e/workflow.spec.ts', import.meta.url),
100
+ 'utf8'
101
+ );
102
+ assert.doesNotMatch(workflowSource, /docs\/architecture|evidenceDirectory/);
103
+ });
104
+
105
+ test('E2E experience fixtures explicitly own every tested route', () => {
106
+ const resourceFixture = readFileSync(
107
+ new URL('../e2e/resource-experience-fixture.tsx', import.meta.url),
108
+ 'utf8'
109
+ );
110
+ for (let index = 1; index <= 12; index += 1) {
111
+ const code = `resource-${String(index).padStart(2, '0')}`;
112
+ const pageCode = `resource:${code}:list`;
113
+ assert.match(resourceFixture, new RegExp(`pageCode: '${pageCode}'`));
114
+ assert.match(resourceFixture, new RegExp(`code: '${pageCode}'`));
115
+ }
116
+
117
+ const workflowFixture = readFileSync(
118
+ new URL('../e2e/workflow-experience-fixture.tsx', import.meta.url),
119
+ 'utf8'
120
+ );
121
+ assert.match(workflowFixture, /code: 'workflow:work-center'/);
122
+ assert.match(workflowFixture, /code: 'workflow:task'/);
123
+ assert.match(workflowFixture, /code: 'workflow:instance'/);
124
+ assert.match(workflowFixture, /path: '\/work-center'/);
125
+ assert.match(workflowFixture, /path: '\/tasks\/:taskId'/);
126
+ assert.match(workflowFixture, /path: '\/workflows\/:instanceId'/);
127
+ assert.doesNotMatch(
128
+ workflowFixture,
129
+ /path: '\/(?:m\/)?admin\/(?:todos|work-center|tasks|workflows)/
130
+ );
131
+ });
132
+
133
+ test('browser acceptance serializes the prewarmed Vite HTML entrypoints', () => {
134
+ const viteConfig = readFileSync(new URL('../vite.config.ts', import.meta.url), 'utf8');
135
+ const playwrightConfig = readFileSync(
136
+ new URL('../playwright.config.ts', import.meta.url),
137
+ 'utf8'
138
+ );
139
+ const htmlEntries = readdirSync(new URL('..', import.meta.url))
140
+ .filter(name => name.endsWith('.e2e.html'))
141
+ .sort();
142
+ assert.deepEqual(htmlEntries, [
143
+ 'data-api-live.e2e.html',
144
+ 'field-protocol.e2e.html',
145
+ 'resource-experience.e2e.html',
146
+ 'workflow-experience.e2e.html',
147
+ ]);
148
+ for (const entry of htmlEntries) {
149
+ assert.ok(viteConfig.includes(`'${entry}'`), `missing optimizeDeps entry ${entry}`);
150
+ }
151
+ assert.match(playwrightConfig, /workers:\s*1/);
152
+ });
153
+
154
+ test('the platform package owns Web build verification policy', () => {
155
+ const verifier = readFileSync(
156
+ new URL('../scripts/verify-build.mjs', import.meta.url),
157
+ 'utf8'
158
+ );
159
+ assert.match(verifier, /verifyOpenXiangdaWebBuild/);
160
+ assert.doesNotMatch(verifier, /2_650_000|800_000|600_000/);
161
+ });
@@ -0,0 +1,68 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawn } from 'node:child_process';
3
+ import { createRequire } from 'node:module';
4
+ import { createServer } from 'node:net';
5
+ import { networkInterfaces } from 'node:os';
6
+ import { dirname, join, resolve } from 'node:path';
7
+ import test from 'node:test';
8
+
9
+ const require = createRequire(import.meta.url);
10
+
11
+ test('Vite is reachable on loopback and not on a LAN interface', async t => {
12
+ const port = await freePort();
13
+ const root = resolve(import.meta.dirname, '..');
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'], {
18
+ cwd: root,
19
+ stdio: 'ignore',
20
+ env: {
21
+ ...process.env,
22
+ OPENXIANGDA_WEB_PORT: String(port),
23
+ OPENXIANGDA_DEV_PROXY: 'http://127.0.0.1:7001',
24
+ },
25
+ });
26
+ try {
27
+ await waitFor(`http://127.0.0.1:${port}`);
28
+ const lan = Object.values(networkInterfaces())
29
+ .flat()
30
+ .find(address => address?.family === 'IPv4' && !address.internal)?.address;
31
+ if (!lan) {
32
+ t.diagnostic('No non-loopback IPv4 interface; loopback reachability verified.');
33
+ return;
34
+ }
35
+ await assert.rejects(
36
+ fetch(`http://${lan}:${port}`, {
37
+ signal: AbortSignal.timeout(500),
38
+ })
39
+ );
40
+ } finally {
41
+ child.kill('SIGTERM');
42
+ await new Promise(resolve => child.once('exit', resolve));
43
+ }
44
+ });
45
+
46
+ async function freePort() {
47
+ return await new Promise<number>((resolve, reject) => {
48
+ const server = createServer();
49
+ server.once('error', reject);
50
+ server.listen({ host: '127.0.0.1', port: 0 }, () => {
51
+ const address = server.address();
52
+ const port = typeof address === 'object' && address ? address.port : 0;
53
+ server.close(error => (error ? reject(error) : resolve(port)));
54
+ });
55
+ });
56
+ }
57
+
58
+ async function waitFor(url: string) {
59
+ const deadline = Date.now() + 10_000;
60
+ while (Date.now() < deadline) {
61
+ try {
62
+ const response = await fetch(url);
63
+ if (response.ok) return;
64
+ } catch {}
65
+ await new Promise(resolve => setTimeout(resolve, 100));
66
+ }
67
+ throw new Error(`VITE_READINESS_TIMEOUT:${url}`);
68
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "jsx": "react-jsx",
7
+ "noEmit": true,
8
+ "exactOptionalPropertyTypes": false,
9
+ "types": ["vite/client", "node"]
10
+ },
11
+ "include": ["src", "test", "vite.config.ts", "playwright.config.ts"]
12
+ }
@@ -0,0 +1,41 @@
1
+ import react from '@vitejs/plugin-react';
2
+ import tailwindcss from '@tailwindcss/vite';
3
+ import { defineConfig } from 'vite';
4
+
5
+ const connectedProxy =
6
+ process.env.OPENXIANGDA_DEV_PROXY?.trim() || 'http://127.0.0.1:7001';
7
+ const port = Number(process.env.OPENXIANGDA_WEB_PORT || 5173);
8
+
9
+ export default defineConfig({
10
+ plugins: [react(), tailwindcss()],
11
+ base: './',
12
+ optimizeDeps: {
13
+ entries: [
14
+ 'index.html',
15
+ 'data-api-live.e2e.html',
16
+ 'field-protocol.e2e.html',
17
+ 'resource-experience.e2e.html',
18
+ 'workflow-experience.e2e.html',
19
+ ],
20
+ },
21
+ server: {
22
+ host: '127.0.0.1',
23
+ port,
24
+ strictPort: true,
25
+ proxy: {
26
+ '/service': { target: connectedProxy, changeOrigin: false },
27
+ '/api': { target: connectedProxy, changeOrigin: false },
28
+ },
29
+ },
30
+ build: {
31
+ sourcemap: false,
32
+ rollupOptions: {
33
+ output: {
34
+ manualChunks: {
35
+ react: ['react', 'react-dom', 'react-router-dom'],
36
+ refine: ['@refinedev/core'],
37
+ },
38
+ },
39
+ },
40
+ },
41
+ });
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="openxiangda-runtime-base" content="/" />
7
+ <meta name="openxiangda-app-code" content="openxiangda-application" />
8
+ <meta name="openxiangda-environment" content="preproduction" />
9
+ <title>Workflow experience acceptance</title>
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ <script type="module" src="/e2e/workflow-experience-fixture.tsx"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,36 @@
1
+ ---
2
+ schema: openxiangda.appspec/app/v1
3
+ app: "openxiangda-application"
4
+ title: "OpenXiangda 应用"
5
+ status: active
6
+ ---
7
+ # OpenXiangda 应用
8
+
9
+ AppSpec 是可选的业务意图辅助层,不是发布门禁。资源、字段、权限和动作仍以
10
+ `openxiangda.config.ts` 与实时编译合同为准。
11
+
12
+ ## 业务目标
13
+
14
+ <!-- 用业务语言说明应用解决的问题和可观察结果。 -->
15
+
16
+ ## 角色
17
+
18
+ <!-- 只记录业务角色及其目标;稳定 role code 在能力规格中引用。 -->
19
+
20
+ ## 范围
21
+
22
+ ### 包含
23
+
24
+ ### 不包含
25
+
26
+ ## 能力目录
27
+
28
+ <!-- 复杂业务再在 capabilities/ 下增加 CAP-*;小应用可以只维护本文件。 -->
29
+
30
+ ## 术语
31
+
32
+ ## 跨能力约束
33
+
34
+ ## 未确认问题
35
+
36
+ - 无。
@@ -0,0 +1,77 @@
1
+ import { defineAdminNavigation, defineOpenXiangdaApp } from 'openxiangda/config';
2
+
3
+ /** Clean application scaffold: declare resources and fields, then generate CRUD. */
4
+ export default defineOpenXiangdaApp({
5
+ schemaVersion: 3,
6
+ app: { code: 'openxiangda-application', name: 'OpenXiangda 应用' },
7
+ frontend: {
8
+ root: 'apps/web',
9
+ devicePolicy: {
10
+ kind: 'viewport-family',
11
+ mobileMaxWidthPx: 900,
12
+ desktopMinWidthPx: 901,
13
+ },
14
+ routes: [
15
+ {
16
+ code: 'application-home',
17
+ path: '/home',
18
+ label: '应用首页',
19
+ surface: 'user',
20
+ },
21
+ {
22
+ code: 'application-home-mobile',
23
+ path: '/m/home',
24
+ label: '移动首页',
25
+ surface: 'user',
26
+ },
27
+ ],
28
+ authentication: {
29
+ accountMode: 'existing-platform-users-only',
30
+ registration: { mode: 'reject' },
31
+ methods: [
32
+ {
33
+ code: 'password',
34
+ type: 'password',
35
+ label: '账号密码登录',
36
+ presentation: 'primary',
37
+ required: true,
38
+ },
39
+ ],
40
+ surfaces: {
41
+ desktop: {
42
+ routeCode: 'application-login',
43
+ path: '/login',
44
+ defaultRouteCode: 'application-home',
45
+ },
46
+ mobile: {
47
+ routeCode: 'application-login-mobile',
48
+ path: '/m/login',
49
+ defaultRouteCode: 'application-home-mobile',
50
+ },
51
+ },
52
+ },
53
+ admin: { navigation: defineAdminNavigation([]) },
54
+ },
55
+ backend: {
56
+ root: 'apps/server',
57
+ runtime: 'node',
58
+ framework: 'nestjs',
59
+ enabled: false,
60
+ isolation: 'shared',
61
+ resourceProfile: 'light',
62
+ secrets: [],
63
+ operations: [],
64
+ },
65
+ platform: { root: 'platform' },
66
+ authz: {
67
+ capabilities: [],
68
+ roles: [],
69
+ scopeDimensions: [],
70
+ scopeSources: [],
71
+ dataPolicies: [],
72
+ authorizationTransitions: [],
73
+ },
74
+ data: { resources: [] },
75
+ events: { schemas: [], subscriptions: [], timers: [], dateTriggers: [] },
76
+ workflows: { definitions: [], bindings: [], activations: [], providers: [], editableParameters: [] },
77
+ });