openxiangda-cli 2.0.0-alpha.49 → 2.0.0-alpha.56
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.
- package/README.md +4 -18
- package/bin/run.js +67 -1
- package/dist/base.d.ts +36 -7
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +95 -18
- package/dist/base.js.map +1 -1
- package/dist/commands/check.d.ts +14 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/create.d.ts +23 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +93 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/deploy.d.ts +19 -8
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +29 -16
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts +14 -3
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +4 -8
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/login.d.ts +24 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/{auth/login.js → login.js} +27 -9
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +15 -2
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +2 -2
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/rollback.d.ts +15 -4
- package/dist/commands/rollback.d.ts.map +1 -1
- package/dist/commands/rollback.js +11 -4
- package/dist/commands/rollback.js.map +1 -1
- package/dist/commands/status.d.ts +14 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/create-workspace.d.ts +23 -0
- package/dist/create-workspace.d.ts.map +1 -0
- package/dist/create-workspace.js +279 -0
- package/dist/create-workspace.js.map +1 -0
- package/package.json +14 -7
- package/template/.dockerignore +12 -0
- package/template/.env.example +3 -0
- package/template/AGENTS.md +8 -0
- package/template/README.md +23 -0
- package/template/_gitignore +14 -0
- package/template/apps/server/Dockerfile +19 -0
- package/template/apps/server/package.json +27 -0
- package/template/apps/server/src/app.module.ts +32 -0
- package/template/apps/server/src/instrument-context.controller.ts +25 -0
- package/template/apps/server/src/main.ts +18 -0
- package/template/apps/server/test/smoke.test.ts +28 -0
- package/template/apps/server/tsconfig.json +9 -0
- package/template/apps/web/e2e/instruments.spec.ts +150 -0
- package/template/apps/web/index.html +12 -0
- package/template/apps/web/package.json +33 -0
- package/template/apps/web/playwright.config.ts +21 -0
- package/template/apps/web/scripts/check.mjs +32 -0
- package/template/apps/web/scripts/verify-build.mjs +20 -0
- package/template/apps/web/src/InstrumentDetailPage.tsx +60 -0
- package/template/apps/web/src/InstrumentForm.tsx +322 -0
- package/template/apps/web/src/InstrumentFormPage.tsx +77 -0
- package/template/apps/web/src/InstrumentListPage.tsx +390 -0
- package/template/apps/web/src/Shell.tsx +52 -0
- package/template/apps/web/src/data-provider.ts +42 -0
- package/template/apps/web/src/fields.ts +72 -0
- package/template/apps/web/src/instrument.ts +55 -0
- package/template/apps/web/src/main.tsx +58 -0
- package/template/apps/web/src/platform-client.ts +246 -0
- package/template/apps/web/src/runtime-meta.ts +60 -0
- package/template/apps/web/src/runtime.tsx +73 -0
- package/template/apps/web/src/styles.css +84 -0
- package/template/apps/web/test/contracts.test.ts +234 -0
- package/template/apps/web/test/loopback.test.ts +62 -0
- package/template/apps/web/tsconfig.json +12 -0
- package/template/apps/web/vite.config.ts +32 -0
- package/template/openxiangda.config.ts +77 -0
- package/template/package.json +29 -0
- package/template/packages/contracts/package.json +23 -0
- package/template/packages/contracts/src/generated.ts +129 -0
- package/template/packages/contracts/src/index.ts +1 -0
- package/template/packages/contracts/tsconfig.json +9 -0
- package/template/platform/data/instruments.ts +81 -0
- package/template/pnpm-workspace.yaml +7 -0
- package/template/scripts/verify-template-budget.mjs +37 -0
- package/template/tsconfig.base.json +13 -0
- package/dist/commands/app/create.d.ts +0 -15
- package/dist/commands/app/create.d.ts.map +0 -1
- package/dist/commands/app/create.js +0 -42
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/info.d.ts +0 -9
- package/dist/commands/app/info.d.ts.map +0 -1
- package/dist/commands/app/info.js +0 -10
- package/dist/commands/app/info.js.map +0 -1
- package/dist/commands/app/link.d.ts +0 -10
- package/dist/commands/app/link.d.ts.map +0 -1
- package/dist/commands/app/link.js +0 -16
- package/dist/commands/app/link.js.map +0 -1
- package/dist/commands/app/provision.d.ts +0 -9
- package/dist/commands/app/provision.d.ts.map +0 -1
- package/dist/commands/app/provision.js +0 -10
- package/dist/commands/app/provision.js.map +0 -1
- package/dist/commands/auth/login.d.ts +0 -11
- package/dist/commands/auth/login.d.ts.map +0 -1
- package/dist/commands/auth/login.js.map +0 -1
- package/dist/commands/auth/logout.d.ts +0 -6
- package/dist/commands/auth/logout.d.ts.map +0 -1
- package/dist/commands/auth/logout.js +0 -30
- package/dist/commands/auth/logout.js.map +0 -1
- package/dist/commands/auth/status.d.ts +0 -6
- package/dist/commands/auth/status.d.ts.map +0 -1
- package/dist/commands/auth/status.js +0 -60
- package/dist/commands/auth/status.js.map +0 -1
- package/dist/commands/authz/membership/grant.d.ts +0 -16
- package/dist/commands/authz/membership/grant.d.ts.map +0 -1
- package/dist/commands/authz/membership/grant.js +0 -32
- package/dist/commands/authz/membership/grant.js.map +0 -1
- package/dist/commands/authz/membership/list.d.ts +0 -16
- package/dist/commands/authz/membership/list.d.ts.map +0 -1
- package/dist/commands/authz/membership/list.js +0 -34
- package/dist/commands/authz/membership/list.js.map +0 -1
- package/dist/commands/authz/membership/revoke.d.ts +0 -15
- package/dist/commands/authz/membership/revoke.d.ts.map +0 -1
- package/dist/commands/authz/membership/revoke.js +0 -22
- package/dist/commands/authz/membership/revoke.js.map +0 -1
- package/dist/commands/authz/membership/update.d.ts +0 -18
- package/dist/commands/authz/membership/update.d.ts.map +0 -1
- package/dist/commands/authz/membership/update.js +0 -31
- package/dist/commands/authz/membership/update.js.map +0 -1
- package/dist/commands/authz/relationship/grant.d.ts +0 -20
- package/dist/commands/authz/relationship/grant.d.ts.map +0 -1
- package/dist/commands/authz/relationship/grant.js +0 -40
- package/dist/commands/authz/relationship/grant.js.map +0 -1
- package/dist/commands/authz/relationship/list.d.ts +0 -18
- package/dist/commands/authz/relationship/list.d.ts.map +0 -1
- package/dist/commands/authz/relationship/list.js +0 -43
- package/dist/commands/authz/relationship/list.js.map +0 -1
- package/dist/commands/authz/relationship/revoke.d.ts +0 -15
- package/dist/commands/authz/relationship/revoke.d.ts.map +0 -1
- package/dist/commands/authz/relationship/revoke.js +0 -22
- package/dist/commands/authz/relationship/revoke.js.map +0 -1
- package/dist/commands/authz/relationship/update.d.ts +0 -18
- package/dist/commands/authz/relationship/update.d.ts.map +0 -1
- package/dist/commands/authz/relationship/update.js +0 -28
- package/dist/commands/authz/relationship/update.js.map +0 -1
- package/dist/commands/authz/super-admin/grant.d.ts +0 -12
- package/dist/commands/authz/super-admin/grant.d.ts.map +0 -1
- package/dist/commands/authz/super-admin/grant.js +0 -20
- package/dist/commands/authz/super-admin/grant.js.map +0 -1
- package/dist/commands/authz/super-admin/list.d.ts +0 -15
- package/dist/commands/authz/super-admin/list.d.ts.map +0 -1
- package/dist/commands/authz/super-admin/list.js +0 -27
- package/dist/commands/authz/super-admin/list.js.map +0 -1
- package/dist/commands/authz/super-admin/revoke.d.ts +0 -15
- package/dist/commands/authz/super-admin/revoke.d.ts.map +0 -1
- package/dist/commands/authz/super-admin/revoke.js +0 -21
- package/dist/commands/authz/super-admin/revoke.js.map +0 -1
- package/dist/commands/build.d.ts +0 -11
- package/dist/commands/build.d.ts.map +0 -1
- package/dist/commands/build.js +0 -23
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/cancel.d.ts +0 -12
- package/dist/commands/cancel.d.ts.map +0 -1
- package/dist/commands/cancel.js +0 -12
- package/dist/commands/cancel.js.map +0 -1
- package/dist/commands/dev/reset.d.ts +0 -10
- package/dist/commands/dev/reset.d.ts.map +0 -1
- package/dist/commands/dev/reset.js +0 -17
- package/dist/commands/dev/reset.js.map +0 -1
- package/dist/commands/dev/status.d.ts +0 -9
- package/dist/commands/dev/status.d.ts.map +0 -1
- package/dist/commands/dev/status.js +0 -10
- package/dist/commands/dev/status.js.map +0 -1
- package/dist/commands/dev/stop.d.ts +0 -9
- package/dist/commands/dev/stop.d.ts.map +0 -1
- package/dist/commands/dev/stop.js +0 -10
- package/dist/commands/dev/stop.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -9
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -7
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/environment/start.d.ts +0 -13
- package/dist/commands/environment/start.d.ts.map +0 -1
- package/dist/commands/environment/start.js +0 -22
- package/dist/commands/environment/start.js.map +0 -1
- package/dist/commands/environment/status.d.ts +0 -9
- package/dist/commands/environment/status.d.ts.map +0 -1
- package/dist/commands/environment/status.js +0 -10
- package/dist/commands/environment/status.js.map +0 -1
- package/dist/commands/environment/stop.d.ts +0 -13
- package/dist/commands/environment/stop.d.ts.map +0 -1
- package/dist/commands/environment/stop.js +0 -22
- package/dist/commands/environment/stop.js.map +0 -1
- package/dist/commands/event/delivery/list.d.ts +0 -10
- package/dist/commands/event/delivery/list.d.ts.map +0 -1
- package/dist/commands/event/delivery/list.js +0 -14
- package/dist/commands/event/delivery/list.js.map +0 -1
- package/dist/commands/event/delivery/replay.d.ts +0 -13
- package/dist/commands/event/delivery/replay.d.ts.map +0 -1
- package/dist/commands/event/delivery/replay.js +0 -16
- package/dist/commands/event/delivery/replay.js.map +0 -1
- package/dist/commands/event/subscription/list.d.ts +0 -9
- package/dist/commands/event/subscription/list.d.ts.map +0 -1
- package/dist/commands/event/subscription/list.js +0 -10
- package/dist/commands/event/subscription/list.js.map +0 -1
- package/dist/commands/event/subscription/rotate-secret.d.ts +0 -15
- package/dist/commands/event/subscription/rotate-secret.d.ts.map +0 -1
- package/dist/commands/event/subscription/rotate-secret.js +0 -33
- package/dist/commands/event/subscription/rotate-secret.js.map +0 -1
- package/dist/commands/event/subscription/status.d.ts +0 -14
- package/dist/commands/event/subscription/status.d.ts.map +0 -1
- package/dist/commands/event/subscription/status.js +0 -19
- package/dist/commands/event/subscription/status.js.map +0 -1
- package/dist/commands/event/timer/fire.d.ts +0 -12
- package/dist/commands/event/timer/fire.d.ts.map +0 -1
- package/dist/commands/event/timer/fire.js +0 -12
- package/dist/commands/event/timer/fire.js.map +0 -1
- package/dist/commands/event/timer/list.d.ts +0 -9
- package/dist/commands/event/timer/list.d.ts.map +0 -1
- package/dist/commands/event/timer/list.js +0 -10
- package/dist/commands/event/timer/list.js.map +0 -1
- package/dist/commands/event/timer/status.d.ts +0 -14
- package/dist/commands/event/timer/status.d.ts.map +0 -1
- package/dist/commands/event/timer/status.js +0 -19
- package/dist/commands/event/timer/status.js.map +0 -1
- package/dist/commands/generate.d.ts +0 -10
- package/dist/commands/generate.d.ts.map +0 -1
- package/dist/commands/generate.js +0 -8
- package/dist/commands/generate.js.map +0 -1
- package/dist/commands/oauth/audit/list.d.ts +0 -12
- package/dist/commands/oauth/audit/list.d.ts.map +0 -1
- package/dist/commands/oauth/audit/list.js +0 -20
- package/dist/commands/oauth/audit/list.js.map +0 -1
- package/dist/commands/oauth/client/create.d.ts +0 -13
- package/dist/commands/oauth/client/create.d.ts.map +0 -1
- package/dist/commands/oauth/client/create.js +0 -31
- package/dist/commands/oauth/client/create.js.map +0 -1
- package/dist/commands/oauth/client/list.d.ts +0 -9
- package/dist/commands/oauth/client/list.d.ts.map +0 -1
- package/dist/commands/oauth/client/list.js +0 -10
- package/dist/commands/oauth/client/list.js.map +0 -1
- package/dist/commands/oauth/client/revoke.d.ts +0 -12
- package/dist/commands/oauth/client/revoke.d.ts.map +0 -1
- package/dist/commands/oauth/client/revoke.js +0 -12
- package/dist/commands/oauth/client/revoke.js.map +0 -1
- package/dist/commands/oauth/client/rotate.d.ts +0 -13
- package/dist/commands/oauth/client/rotate.d.ts.map +0 -1
- package/dist/commands/oauth/client/rotate.js +0 -20
- package/dist/commands/oauth/client/rotate.js.map +0 -1
- package/dist/commands/oauth/client/update.d.ts +0 -15
- package/dist/commands/oauth/client/update.d.ts.map +0 -1
- package/dist/commands/oauth/client/update.js +0 -23
- package/dist/commands/oauth/client/update.js.map +0 -1
- package/dist/commands/oauth/runtime/rotate.d.ts +0 -12
- package/dist/commands/oauth/runtime/rotate.d.ts.map +0 -1
- package/dist/commands/oauth/runtime/rotate.js +0 -28
- package/dist/commands/oauth/runtime/rotate.js.map +0 -1
- package/dist/commands/oauth/runtime/status.d.ts +0 -10
- package/dist/commands/oauth/runtime/status.d.ts.map +0 -1
- package/dist/commands/oauth/runtime/status.js +0 -13
- package/dist/commands/oauth/runtime/status.js.map +0 -1
- package/dist/commands/promote.d.ts +0 -13
- package/dist/commands/promote.d.ts.map +0 -1
- package/dist/commands/promote.js +0 -9
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/retry.d.ts +0 -12
- package/dist/commands/retry.d.ts.map +0 -1
- package/dist/commands/retry.js +0 -9
- package/dist/commands/retry.js.map +0 -1
- package/dist/commands/secret/audit/list.d.ts +0 -14
- package/dist/commands/secret/audit/list.d.ts.map +0 -1
- package/dist/commands/secret/audit/list.js +0 -16
- package/dist/commands/secret/audit/list.js.map +0 -1
- package/dist/commands/secret/create.d.ts +0 -18
- package/dist/commands/secret/create.d.ts.map +0 -1
- package/dist/commands/secret/create.js +0 -33
- package/dist/commands/secret/create.js.map +0 -1
- package/dist/commands/secret/delete.d.ts +0 -15
- package/dist/commands/secret/delete.d.ts.map +0 -1
- package/dist/commands/secret/delete.js +0 -20
- package/dist/commands/secret/delete.js.map +0 -1
- package/dist/commands/secret/list.d.ts +0 -10
- package/dist/commands/secret/list.d.ts.map +0 -1
- package/dist/commands/secret/list.js +0 -13
- package/dist/commands/secret/list.js.map +0 -1
- package/dist/commands/secret/rotate.d.ts +0 -18
- package/dist/commands/secret/rotate.d.ts.map +0 -1
- package/dist/commands/secret/rotate.js +0 -30
- package/dist/commands/secret/rotate.js.map +0 -1
- package/dist/commands/secret/update.d.ts +0 -17
- package/dist/commands/secret/update.d.ts.map +0 -1
- package/dist/commands/secret/update.js +0 -37
- package/dist/commands/secret/update.js.map +0 -1
- package/dist/commands/skill/install.d.ts +0 -11
- package/dist/commands/skill/install.d.ts.map +0 -1
- package/dist/commands/skill/install.js +0 -52
- package/dist/commands/skill/install.js.map +0 -1
- package/dist/commands/skill/validate.d.ts +0 -9
- package/dist/commands/skill/validate.d.ts.map +0 -1
- package/dist/commands/skill/validate.js +0 -32
- package/dist/commands/skill/validate.js.map +0 -1
- package/dist/commands/test.d.ts +0 -9
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js +0 -7
- package/dist/commands/test.js.map +0 -1
- package/dist/commands/workflow/provider/list.d.ts +0 -10
- package/dist/commands/workflow/provider/list.d.ts.map +0 -1
- package/dist/commands/workflow/provider/list.js +0 -13
- package/dist/commands/workflow/provider/list.js.map +0 -1
- package/dist/commands/workflow/provider/rotate.d.ts +0 -15
- package/dist/commands/workflow/provider/rotate.d.ts.map +0 -1
- package/dist/commands/workflow/provider/rotate.js +0 -24
- package/dist/commands/workflow/provider/rotate.js.map +0 -1
- package/dist/native-authz.d.ts +0 -4
- package/dist/native-authz.d.ts.map +0 -1
- package/dist/native-authz.js +0 -25
- package/dist/native-authz.js.map +0 -1
- package/dist/secret-value.d.ts +0 -5
- package/dist/secret-value.d.ts.map +0 -1
- package/dist/secret-value.js +0 -15
- package/dist/secret-value.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.56",
|
|
4
4
|
"description": "Thin application-level CLI for OpenXiangda 2.0.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
8
15
|
"bin": {
|
|
9
16
|
"openxiangda": "./bin/run.js"
|
|
10
17
|
},
|
|
11
18
|
"files": [
|
|
12
19
|
"bin/",
|
|
13
20
|
"dist/",
|
|
21
|
+
"template/",
|
|
14
22
|
"README.md"
|
|
15
23
|
],
|
|
16
24
|
"dependencies": {
|
|
17
25
|
"@oclif/core": "4.13.3",
|
|
18
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
19
|
-
"
|
|
20
|
-
"openxiangda-
|
|
21
|
-
"openxiangda-skill-kit": "2.0.0-alpha.39"
|
|
26
|
+
"openxiangda-contracts": "2.0.0-alpha.26",
|
|
27
|
+
"openxiangda-devkit-core": "2.0.0-alpha.33",
|
|
28
|
+
"openxiangda-mcp": "2.0.0-alpha.33"
|
|
22
29
|
},
|
|
23
30
|
"devDependencies": {
|
|
24
31
|
"tsx": "4.23.12",
|
|
@@ -35,9 +42,9 @@
|
|
|
35
42
|
},
|
|
36
43
|
"license": "MIT",
|
|
37
44
|
"scripts": {
|
|
38
|
-
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json",
|
|
39
46
|
"build:release": "node ../../scripts/prune-package-dist.mjs && pnpm build",
|
|
40
47
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
41
|
-
"test": "tsx --test test/*.test.ts"
|
|
48
|
+
"test": "node scripts/prepare-template.mjs && tsx --test test/*.test.ts && node ../../scripts/verify-cli-blackbox.mjs"
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Application development rules
|
|
2
|
+
|
|
3
|
+
- Build normal application code directly; generated Skills and historical templates are not design authority.
|
|
4
|
+
- Keep CRUD in the replaceable browser Data API adapter under `/service`.
|
|
5
|
+
- Put only real transactional or integration actions in Nest under `/api`.
|
|
6
|
+
- Read current-user role/capability unions from the platform. UI guards are presentation only; server authorization is authoritative.
|
|
7
|
+
- Do not add shadow scope fields. Use `collegeId` and `instrumentAdminIds` directly.
|
|
8
|
+
- Run `openxiangda check` before delivery.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# OpenXiangda 2.0 仪器应用
|
|
2
|
+
|
|
3
|
+
这是最小黄金模板:Vite + React Router + Refine Core + Ant Design 前端、NestJS 后端,以及平台 Data/AuthZ 声明。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
openxiangda login --base-url https://platform.example.com
|
|
7
|
+
openxiangda create instrument-center
|
|
8
|
+
cd instrument-center
|
|
9
|
+
openxiangda dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`instruments` 覆盖 30 字段、列表筛选、服务端分页排序、新增、编辑、详情、删除和文件上传。所有 CRUD 位于 `apps/web/src/platform-client.ts` 的 Data API adapter;Nest 只保留一个无副作用 current-user 摘要动作。
|
|
13
|
+
|
|
14
|
+
前端读取平台 current-user 的角色/能力并集进行展示保护,服务端始终权威。字段策略区分 create/update;无权字段在提交前从 payload 删除。
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
openxiangda check
|
|
18
|
+
openxiangda deploy
|
|
19
|
+
openxiangda status
|
|
20
|
+
openxiangda logs
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`deploy` 使用本模板的 `apps/server/Dockerfile` 自动构建并推送 `linux/amd64` 镜像,再把不可变 digest 写入 AppPackage。请在本机安装并启动 Docker Buildx,并提前通过 Docker credential store 登录平台声明的镜像仓库;不要把 registry 密钥写进工作区。
|
|
@@ -0,0 +1,19 @@
|
|
|
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 pnpm --filter @app/server... install --frozen-lockfile --ignore-scripts
|
|
8
|
+
COPY apps/server apps/server
|
|
9
|
+
COPY packages/contracts packages/contracts
|
|
10
|
+
RUN pnpm --filter @app/server... build
|
|
11
|
+
RUN pnpm --filter @app/server deploy --prod --legacy --ignore-scripts /output
|
|
12
|
+
|
|
13
|
+
FROM node:22-alpine AS runtime
|
|
14
|
+
ENV NODE_ENV=production
|
|
15
|
+
WORKDIR /app
|
|
16
|
+
COPY --from=build --chown=1000:1000 /output /app
|
|
17
|
+
USER 1000:1000
|
|
18
|
+
EXPOSE 3000
|
|
19
|
+
CMD ["node", "dist/main.js"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app/server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/main.ts",
|
|
8
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
9
|
+
"test": "tsx --test test/*.test.ts",
|
|
10
|
+
"build": "tsc -p tsconfig.json",
|
|
11
|
+
"start": "node dist/main.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@app/contracts": "workspace:*",
|
|
15
|
+
"@nestjs/common": "11.1.29",
|
|
16
|
+
"@nestjs/core": "11.1.29",
|
|
17
|
+
"@nestjs/platform-fastify": "11.1.29",
|
|
18
|
+
"openxiangda-nest": "2.0.0-alpha.31",
|
|
19
|
+
"reflect-metadata": "0.2.2",
|
|
20
|
+
"rxjs": "7.8.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "24.13.3",
|
|
24
|
+
"tsx": "4.23.12",
|
|
25
|
+
"typescript": "5.9.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { OpenXiangdaModule } from 'openxiangda-nest';
|
|
3
|
+
import { InstrumentContextController } from './instrument-context.controller.js';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
imports: [
|
|
7
|
+
OpenXiangdaModule.forRoot({
|
|
8
|
+
appCode: process.env.OPENXIANGDA_APP_CODE || 'instrument-center',
|
|
9
|
+
platformBaseUrl:
|
|
10
|
+
process.env.OPENXIANGDA_PLATFORM_BASE_URL ||
|
|
11
|
+
'http://127.0.0.1:7001/service',
|
|
12
|
+
environmentKey:
|
|
13
|
+
process.env.OPENXIANGDA_ENVIRONMENT_KEY || 'preproduction',
|
|
14
|
+
environmentId:
|
|
15
|
+
process.env.OPENXIANGDA_ENVIRONMENT_ID || 'connected-development',
|
|
16
|
+
appVersionId:
|
|
17
|
+
process.env.OPENXIANGDA_APP_VERSION_ID || 'connected-development',
|
|
18
|
+
deploymentRunId:
|
|
19
|
+
process.env.OPENXIANGDA_DEPLOYMENT_RUN_ID || 'connected-development',
|
|
20
|
+
environmentHeadRevision: Number(
|
|
21
|
+
process.env.OPENXIANGDA_ENVIRONMENT_HEAD_REVISION || 1
|
|
22
|
+
),
|
|
23
|
+
backendRevisionId:
|
|
24
|
+
process.env.OPENXIANGDA_BACKEND_REVISION_ID || 'connected-development',
|
|
25
|
+
version: process.env.OPENXIANGDA_APP_VERSION || '0.1.0-dev',
|
|
26
|
+
connectedDevelopment:
|
|
27
|
+
process.env.OPENXIANGDA_CONNECTED_DEV === 'true',
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
controllers: [InstrumentContextController],
|
|
31
|
+
})
|
|
32
|
+
export class AppModule {}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Controller, Get, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { appOperations } from '@app/contracts';
|
|
3
|
+
import {
|
|
4
|
+
CurrentUser,
|
|
5
|
+
OpenXiangdaAuthzGuard,
|
|
6
|
+
OpenXiangdaOperation,
|
|
7
|
+
type OpenXiangdaCurrentUser,
|
|
8
|
+
} from 'openxiangda-nest';
|
|
9
|
+
|
|
10
|
+
@Controller('api/instruments')
|
|
11
|
+
@UseGuards(OpenXiangdaAuthzGuard)
|
|
12
|
+
export class InstrumentContextController {
|
|
13
|
+
@Get('context')
|
|
14
|
+
@OpenXiangdaOperation(appOperations.instrumentContextInspect)
|
|
15
|
+
inspect(@CurrentUser() user: OpenXiangdaCurrentUser) {
|
|
16
|
+
// Intentionally read-only: this demonstrates local Nest business actions
|
|
17
|
+
// without wrapping Data API CRUD.
|
|
18
|
+
return {
|
|
19
|
+
userId: user.userId,
|
|
20
|
+
roleCodes: user.roleCodes,
|
|
21
|
+
capabilityCodes: user.capabilityCodes,
|
|
22
|
+
environmentKey: user.environmentKey,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { NestFactory } from '@nestjs/core';
|
|
3
|
+
import {
|
|
4
|
+
FastifyAdapter,
|
|
5
|
+
type NestFastifyApplication,
|
|
6
|
+
} from '@nestjs/platform-fastify';
|
|
7
|
+
import { AppModule } from './app.module.js';
|
|
8
|
+
|
|
9
|
+
const app = await NestFactory.create<NestFastifyApplication>(
|
|
10
|
+
AppModule,
|
|
11
|
+
new FastifyAdapter({ trustProxy: true })
|
|
12
|
+
);
|
|
13
|
+
app.enableShutdownHooks();
|
|
14
|
+
const production = process.env.NODE_ENV === 'production';
|
|
15
|
+
await app.listen(
|
|
16
|
+
Number(process.env.OPENXIANGDA_APP_PORT || process.env.PORT || 3000),
|
|
17
|
+
production ? '0.0.0.0' : '127.0.0.1'
|
|
18
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import test from 'node:test';
|
|
4
|
+
import { InstrumentContextController } from '../src/instrument-context.controller.js';
|
|
5
|
+
|
|
6
|
+
test('the example action returns only the verified current-user summary', () => {
|
|
7
|
+
const result = new InstrumentContextController().inspect({
|
|
8
|
+
userId: 'user-1',
|
|
9
|
+
appCode: 'instrument-center',
|
|
10
|
+
environmentKey: 'preproduction',
|
|
11
|
+
roleCodes: ['college_admin', 'instrument_admin'],
|
|
12
|
+
isAppSuperAdmin: false,
|
|
13
|
+
capabilityCodes: ['app:instrument-center:data:instruments:read'],
|
|
14
|
+
});
|
|
15
|
+
assert.deepEqual(result.roleCodes, ['college_admin', 'instrument_admin']);
|
|
16
|
+
assert.equal(result.userId, 'user-1');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('the server template owns no CRUD, event, OAuth, or secret implementation', () => {
|
|
20
|
+
const moduleSource = readFileSync(new URL('../src/app.module.ts', import.meta.url), 'utf8');
|
|
21
|
+
const controllerSource = readFileSync(
|
|
22
|
+
new URL('../src/instrument-context.controller.ts', import.meta.url),
|
|
23
|
+
'utf8'
|
|
24
|
+
);
|
|
25
|
+
assert.match(controllerSource, /@CurrentUser\(\)/);
|
|
26
|
+
assert.doesNotMatch(moduleSource + controllerSource, /oauthClient|eventSigning|RuntimeSecrets|Purchase|Workflow/);
|
|
27
|
+
assert.doesNotMatch(controllerSource, /createData|updateData|deleteData|queryData/);
|
|
28
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test('renders the production warning and never requests a role session', async ({
|
|
4
|
+
page,
|
|
5
|
+
}) => {
|
|
6
|
+
const requests: string[] = [];
|
|
7
|
+
page.on('request', request => requests.push(request.url()));
|
|
8
|
+
await page.route('**/view/instrument-center/instruments', async route => {
|
|
9
|
+
if (route.request().resourceType() !== 'document') {
|
|
10
|
+
await route.continue();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const response = await route.fetch();
|
|
14
|
+
const body = (await response.text()).replace(
|
|
15
|
+
'<head>',
|
|
16
|
+
`<head>
|
|
17
|
+
<meta name="openxiangda-runtime-base" content="/view/instrument-center/" />
|
|
18
|
+
<meta name="openxiangda-app-code" content="instrument-center" />
|
|
19
|
+
<meta name="openxiangda-environment" content="production" />`
|
|
20
|
+
);
|
|
21
|
+
await route.fulfill({ response, body });
|
|
22
|
+
});
|
|
23
|
+
await page.route('**/service/**', async route => {
|
|
24
|
+
const url = new URL(route.request().url());
|
|
25
|
+
if (
|
|
26
|
+
url.pathname.endsWith('/native/current-user') &&
|
|
27
|
+
url.searchParams.get('environmentKey') === 'production'
|
|
28
|
+
) {
|
|
29
|
+
await route.fulfill({
|
|
30
|
+
contentType: 'application/json',
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
code: 200,
|
|
33
|
+
data: {
|
|
34
|
+
schemaVersion: 'openxiangda.current-user/v2',
|
|
35
|
+
appCode: 'instrument-center',
|
|
36
|
+
environment: {
|
|
37
|
+
id: 'production-id',
|
|
38
|
+
key: 'production',
|
|
39
|
+
activeAppVersionId: 'app-version-1',
|
|
40
|
+
headRevision: 7,
|
|
41
|
+
authzRevisionId: 'authz-revision-1',
|
|
42
|
+
authzVersion: 3,
|
|
43
|
+
scopeDataVersion: 'scope-data-version-1',
|
|
44
|
+
},
|
|
45
|
+
principal: {
|
|
46
|
+
type: 'user',
|
|
47
|
+
userId: 'user-e2e',
|
|
48
|
+
roleCodes: ['college_admin', 'instrument_admin'],
|
|
49
|
+
capabilityCodes: [
|
|
50
|
+
'app:instrument-center:data:instruments:read',
|
|
51
|
+
'app:instrument-center:data:instruments:update',
|
|
52
|
+
'app:instrument-center:instrument:context:read',
|
|
53
|
+
],
|
|
54
|
+
isAppSuperAdmin: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (url.pathname.endsWith('/native/data/instruments/query')) {
|
|
62
|
+
expect(route.request().postDataJSON()).toMatchObject({
|
|
63
|
+
schemaVersion: 'openxiangda.data-query/v2',
|
|
64
|
+
environmentKey: 'production',
|
|
65
|
+
});
|
|
66
|
+
await route.fulfill({
|
|
67
|
+
contentType: 'application/json',
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
code: 200,
|
|
70
|
+
data: {
|
|
71
|
+
schemaVersion: 'openxiangda.data-page/v2',
|
|
72
|
+
resourceCode: 'instruments',
|
|
73
|
+
items: [
|
|
74
|
+
{
|
|
75
|
+
id: 'instrument-1',
|
|
76
|
+
revision: 2,
|
|
77
|
+
instrumentCode: 'INS-001',
|
|
78
|
+
chineseName: '场发射扫描电子显微镜',
|
|
79
|
+
specModel: 'SU8600',
|
|
80
|
+
categoryId: 'analysis_microscope',
|
|
81
|
+
assetCode: 'ASSET-001',
|
|
82
|
+
enabledDate: '2025-01-01',
|
|
83
|
+
usageStatus: 'active',
|
|
84
|
+
instrumentStatus: 'normal',
|
|
85
|
+
openToOutside: true,
|
|
86
|
+
collegeId: 'college-am',
|
|
87
|
+
manageDepartmentId: 'dept-am-test',
|
|
88
|
+
useDepartmentId: 'dept-am-test',
|
|
89
|
+
instrumentAdminIds: ['user-e2e'],
|
|
90
|
+
contactUserIds: ['user-e2e'],
|
|
91
|
+
contactPhone: '13800138000',
|
|
92
|
+
updatedAt: '2026-08-21T00:00:00.000Z',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
total: 1,
|
|
96
|
+
limit: 20,
|
|
97
|
+
offset: 0,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await route.fulfill({
|
|
104
|
+
status: 404,
|
|
105
|
+
contentType: 'application/json',
|
|
106
|
+
body: JSON.stringify({ code: 404, message: 'not mocked' }),
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
await page.goto('/view/instrument-center/instruments');
|
|
110
|
+
await expect(page.getByTestId('production-data-warning')).toBeVisible();
|
|
111
|
+
await expect(page.getByTestId('instrument-title')).toHaveText('仪器资源');
|
|
112
|
+
await expect(page.getByText('场发射扫描电子显微镜')).toBeVisible();
|
|
113
|
+
await expect(page.getByText('college_admin、instrument_admin')).toBeVisible();
|
|
114
|
+
expect(requests.filter(url => url.includes('role-session'))).toHaveLength(0);
|
|
115
|
+
expect(
|
|
116
|
+
requests.some(url =>
|
|
117
|
+
url.includes('/native/current-user?environmentKey=production')
|
|
118
|
+
)
|
|
119
|
+
).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('keeps the production warning visible when current-user fails closed', async ({
|
|
123
|
+
page,
|
|
124
|
+
}) => {
|
|
125
|
+
await page.route('**/view/instrument-center/instruments', async route => {
|
|
126
|
+
if (route.request().resourceType() !== 'document') {
|
|
127
|
+
await route.continue();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const response = await route.fetch();
|
|
131
|
+
const body = (await response.text()).replace(
|
|
132
|
+
'<head>',
|
|
133
|
+
`<head>
|
|
134
|
+
<meta name="openxiangda-runtime-base" content="/view/instrument-center/" />
|
|
135
|
+
<meta name="openxiangda-app-code" content="instrument-center" />
|
|
136
|
+
<meta name="openxiangda-environment" content="production" />`
|
|
137
|
+
);
|
|
138
|
+
await route.fulfill({ response, body });
|
|
139
|
+
});
|
|
140
|
+
await page.route('**/service/**', route =>
|
|
141
|
+
route.fulfill({
|
|
142
|
+
status: 503,
|
|
143
|
+
contentType: 'application/json',
|
|
144
|
+
body: JSON.stringify({ code: 503, message: 'current-user unavailable' }),
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
await page.goto('/view/instrument-center/instruments');
|
|
148
|
+
await expect(page.getByTestId('production-data-warning')).toBeVisible();
|
|
149
|
+
await expect(page.getByText('无法读取平台当前用户')).toBeVisible();
|
|
150
|
+
});
|
|
@@ -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>仪器资源管理</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,33 @@
|
|
|
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
|
+
"dayjs": "1.11.18",
|
|
18
|
+
"openxiangda-contracts": "2.0.0-alpha.26",
|
|
19
|
+
"react": "19.2.8",
|
|
20
|
+
"react-dom": "19.2.8",
|
|
21
|
+
"react-router-dom": "7.8.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@playwright/test": "1.62.1",
|
|
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
|
+
"tsx": "4.23.12",
|
|
30
|
+
"typescript": "5.9.3",
|
|
31
|
+
"vite": "7.1.3"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const baseURL =
|
|
4
|
+
process.env.OPENXIANGDA_E2E_BASE_URL || 'http://127.0.0.1:4173';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
testDir: './e2e',
|
|
8
|
+
use: { baseURL, trace: 'retain-on-failure' },
|
|
9
|
+
webServer: process.env.OPENXIANGDA_E2E_BASE_URL
|
|
10
|
+
? undefined
|
|
11
|
+
: {
|
|
12
|
+
command: 'pnpm dev',
|
|
13
|
+
url: baseURL,
|
|
14
|
+
reuseExistingServer: false,
|
|
15
|
+
env: {
|
|
16
|
+
OPENXIANGDA_WEB_PORT: '4173',
|
|
17
|
+
OPENXIANGDA_ENVIRONMENT_KEY: 'production',
|
|
18
|
+
OPENXIANGDA_DEV_PROXY: 'http://127.0.0.1:7001',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
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 (
|
|
10
|
+
entry.isDirectory() &&
|
|
11
|
+
!['.umi', '.umi-production', '.turbopack'].includes(entry.name)
|
|
12
|
+
)
|
|
13
|
+
visit(path);
|
|
14
|
+
else if (/\.(?:ts|tsx)$/.test(entry.name)) files.push(path);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
visit(join(root, 'src'));
|
|
18
|
+
const lines = files.reduce(
|
|
19
|
+
(total, file) => total + readFileSync(file, 'utf8').split(/\r?\n/).length,
|
|
20
|
+
0
|
|
21
|
+
);
|
|
22
|
+
if (files.length > 18) throw new Error(`WEB_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>18`);
|
|
23
|
+
if (lines > 1_600) throw new Error(`WEB_BUSINESS_LOC_BUDGET_EXCEEDED:${lines}>1600`);
|
|
24
|
+
const source = files.map(file => readFileSync(file, 'utf8')).join('\n');
|
|
25
|
+
for (const marker of ['@umijs/', 'openxiangda-' + 'admin', 'openxiangda-' + 'user', 'instrument_query', 'instrument_save', 'function.invoke', 'role' + '-session']) {
|
|
26
|
+
if (source.includes(marker)) throw new Error(`WEB_FORBIDDEN_MARKER:${marker}`);
|
|
27
|
+
}
|
|
28
|
+
const playwright = readFileSync(join(root, 'playwright.config.ts'), 'utf8');
|
|
29
|
+
if (!playwright.includes('reuseExistingServer: false')) {
|
|
30
|
+
throw new Error('WEB_E2E_MUST_NOT_REUSE_UNKNOWN_SERVER');
|
|
31
|
+
}
|
|
32
|
+
process.stdout.write(`Verified Vite/Refine web source: ${files.length} files, ${lines} LOC.\n`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { gzipSync } from 'node:zlib';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const dist = resolve(import.meta.dirname, '../dist');
|
|
6
|
+
const assets = join(dist, 'assets');
|
|
7
|
+
const names = readdirSync(assets);
|
|
8
|
+
const files = names.map(name => join(assets, name));
|
|
9
|
+
const total = files.reduce((bytes, file) => bytes + statSync(file).size, 0);
|
|
10
|
+
if (total > 2_500_000) throw new Error(`WEB_DIST_BUDGET_EXCEEDED:${total}>2500000`);
|
|
11
|
+
const javascript = files.filter(file => file.endsWith('.js'));
|
|
12
|
+
const gzip = javascript.reduce(
|
|
13
|
+
(bytes, file) => bytes + gzipSync(readFileSync(file), { level: 9 }).byteLength,
|
|
14
|
+
0
|
|
15
|
+
);
|
|
16
|
+
if (gzip > 600_000) throw new Error(`WEB_JS_GZIP_BUDGET_EXCEEDED:${gzip}>600000`);
|
|
17
|
+
if (names.some(name => name.endsWith('.map'))) throw new Error('WEB_SOURCE_MAP_FORBIDDEN');
|
|
18
|
+
const html = readFileSync(join(dist, 'index.html'), 'utf8');
|
|
19
|
+
if (!html.includes('<div id="root"></div>')) throw new Error('WEB_ROOT_MISSING');
|
|
20
|
+
process.stdout.write(`Verified Vite build: ${total} bytes, ${gzip} gzip bytes.\n`);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useOne } from '@refinedev/core';
|
|
2
|
+
import { Button, Result, Space, Spin, Typography } from 'antd';
|
|
3
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
4
|
+
import { InstrumentForm } from './InstrumentForm';
|
|
5
|
+
import { Shell } from './Shell';
|
|
6
|
+
import { INSTRUMENT_CAPABILITIES, type InstrumentRecord } from './instrument';
|
|
7
|
+
import { useRuntime } from './runtime';
|
|
8
|
+
|
|
9
|
+
export function InstrumentDetailPage() {
|
|
10
|
+
const { id = '' } = useParams();
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
const { hasCapability } = useRuntime();
|
|
13
|
+
const query = useOne<InstrumentRecord>({
|
|
14
|
+
resource: 'instruments',
|
|
15
|
+
id,
|
|
16
|
+
queryOptions: { retry: false },
|
|
17
|
+
});
|
|
18
|
+
const record = query.result;
|
|
19
|
+
if (query.query.isLoading)
|
|
20
|
+
return (
|
|
21
|
+
<Shell>
|
|
22
|
+
<Spin />
|
|
23
|
+
</Shell>
|
|
24
|
+
);
|
|
25
|
+
if (query.query.isError || !record)
|
|
26
|
+
return (
|
|
27
|
+
<Shell>
|
|
28
|
+
<Result
|
|
29
|
+
status="403"
|
|
30
|
+
title="无法访问"
|
|
31
|
+
subTitle={query.query.error?.message || '记录不存在'}
|
|
32
|
+
/>
|
|
33
|
+
</Shell>
|
|
34
|
+
);
|
|
35
|
+
return (
|
|
36
|
+
<Shell>
|
|
37
|
+
<Space className="oxa-page-heading">
|
|
38
|
+
<Typography.Title level={3} style={{ margin: 0 }}>
|
|
39
|
+
仪器详情
|
|
40
|
+
</Typography.Title>
|
|
41
|
+
<Space>
|
|
42
|
+
<Button onClick={() => navigate('/instruments')}>返回列表</Button>
|
|
43
|
+
{hasCapability(INSTRUMENT_CAPABILITIES.update) && (
|
|
44
|
+
<Button
|
|
45
|
+
type="primary"
|
|
46
|
+
onClick={() => navigate(`/instruments/${id}/edit`)}
|
|
47
|
+
>
|
|
48
|
+
编辑
|
|
49
|
+
</Button>
|
|
50
|
+
)}
|
|
51
|
+
</Space>
|
|
52
|
+
</Space>
|
|
53
|
+
<InstrumentForm
|
|
54
|
+
mode="detail"
|
|
55
|
+
record={record}
|
|
56
|
+
onCancel={() => navigate('/instruments')}
|
|
57
|
+
/>
|
|
58
|
+
</Shell>
|
|
59
|
+
);
|
|
60
|
+
}
|