openxiangda-cli 2.0.0-alpha.86 → 2.0.0-alpha.90

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 (78) hide show
  1. package/README.md +2 -2
  2. package/bin/run.js +1 -0
  3. package/dist/commands/skill.d.ts +26 -0
  4. package/dist/commands/skill.d.ts.map +1 -0
  5. package/dist/commands/skill.js +43 -0
  6. package/dist/commands/skill.js.map +1 -0
  7. package/dist/create-workspace.d.ts.map +1 -1
  8. package/dist/create-workspace.js +3 -3
  9. package/dist/create-workspace.js.map +1 -1
  10. package/dist/toolchain-capsule.d.ts.map +1 -1
  11. package/dist/toolchain-capsule.js +4 -8
  12. package/dist/toolchain-capsule.js.map +1 -1
  13. package/package.json +6 -7
  14. package/template/AGENTS.md +2 -1
  15. package/template/README.md +5 -5
  16. package/template/apps/server/package.json +1 -1
  17. package/template/apps/server/src/app.module.ts +14 -1
  18. package/template/apps/server/src/main.ts +1 -1
  19. package/template/apps/server/test/smoke.test.ts +3 -1
  20. package/template/apps/web/e2e/client-fixture.ts +5 -0
  21. package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
  22. package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
  23. package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
  24. package/template/apps/web/e2e/resources.spec.ts +20 -181
  25. package/template/apps/web/field-protocol.e2e.html +3 -0
  26. package/template/apps/web/package.json +2 -5
  27. package/template/apps/web/src/main.tsx +12 -60
  28. package/template/apps/web/test/contracts.test.ts +52 -104
  29. package/template/openxiangda.config.ts +2 -2
  30. package/template/package.json +1 -3
  31. package/template/packages/contracts/src/generated.ts +27 -0
  32. package/template/scripts/verify-template-budget.mjs +2 -2
  33. package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
  34. package/template/apps/web/src/FilePreviewPage.tsx +0 -324
  35. package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
  36. package/template/apps/web/src/Shell.tsx +0 -445
  37. package/template/apps/web/src/appearance.tsx +0 -147
  38. package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
  39. package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
  40. package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
  41. package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
  42. package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
  43. package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
  44. package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
  45. package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
  46. package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
  47. package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
  48. package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
  49. package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
  50. package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
  51. package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
  52. package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
  53. package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
  54. package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
  55. package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
  56. package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
  57. package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
  58. package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
  59. package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
  60. package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
  61. package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
  62. package/template/apps/web/src/components/resource/resource-import.ts +0 -205
  63. package/template/apps/web/src/data-provider.ts +0 -72
  64. package/template/apps/web/src/platform-client.ts +0 -708
  65. package/template/apps/web/src/runtime-meta.ts +0 -75
  66. package/template/apps/web/src/runtime.tsx +0 -227
  67. package/template/apps/web/src/styles.css +0 -1941
  68. package/template/apps/web/test/address-field.test.ts +0 -66
  69. package/template/apps/web/test/cascade-value.test.ts +0 -68
  70. package/template/apps/web/test/directory-value.test.ts +0 -55
  71. package/template/apps/web/test/field-bounds.test.ts +0 -17
  72. package/template/apps/web/test/field-form-codec.test.ts +0 -85
  73. package/template/apps/web/test/location-field.test.ts +0 -83
  74. package/template/apps/web/test/resource-import.test.ts +0 -143
  75. package/template/apps/web/test/resource-query.test.ts +0 -144
  76. package/template/apps/web/test/rich-json-field.test.ts +0 -53
  77. package/template/apps/web/test/signature-serial-field.test.ts +0 -40
  78. package/template/apps/web/test/subtable-value.test.ts +0 -144
@@ -1,75 +0,0 @@
1
- export interface RuntimeMount {
2
- runtimeBase: string;
3
- appCode: string;
4
- environmentKey: 'preproduction' | 'production';
5
- }
6
-
7
- export function readRuntimeMount(
8
- read: (name: string) => string | undefined
9
- ): RuntimeMount | null {
10
- const runtimeBase = read('openxiangda-runtime-base')?.trim();
11
- const appCode = read('openxiangda-app-code')?.trim();
12
- const environment = read('openxiangda-environment')?.trim();
13
- if (!runtimeBase && !appCode && !environment) return null;
14
- if (
15
- !runtimeBase ||
16
- !appCode ||
17
- (environment !== 'preproduction' && environment !== 'production')
18
- ) {
19
- throw new Error('OPENXIANGDA_RUNTIME_META_INVALID');
20
- }
21
- return { runtimeBase, appCode, environmentKey: environment };
22
- }
23
-
24
- export function runtimeMount() {
25
- if (typeof document === 'undefined') return null;
26
- return readRuntimeMount(name =>
27
- document
28
- .querySelector<HTMLMetaElement>(`meta[name="${name}"]`)
29
- ?.content.trim()
30
- );
31
- }
32
-
33
- export function applicationCode() {
34
- return runtimeMount()?.appCode || 'openxiangda-application';
35
- }
36
-
37
- export function resolveApplicationBasename(mount: RuntimeMount | null) {
38
- if (!mount) return undefined;
39
- const pathname = new URL(mount.runtimeBase, 'https://runtime.local').pathname;
40
- const normalized = pathname.replace(/\/+$/, '');
41
- return normalized || '/';
42
- }
43
-
44
- export function applicationBasename() {
45
- return resolveApplicationBasename(runtimeMount());
46
- }
47
-
48
- export function attachmentPreviewPath(resourceCode: string, fileId: string) {
49
- return resolveAttachmentPreviewPath(runtimeMount(), resourceCode, fileId);
50
- }
51
-
52
- export function resolveAttachmentPreviewPath(
53
- mount: RuntimeMount | null,
54
- resourceCode: string,
55
- fileId: string
56
- ) {
57
- const base = resolveApplicationBasename(mount)?.replace(/\/+$/, '') || '';
58
- return `${base}/files/${encodeURIComponent(resourceCode)}/${encodeURIComponent(
59
- fileId
60
- )}/preview`;
61
- }
62
-
63
- export function resolveApplicationApiPath(
64
- mount: RuntimeMount | null,
65
- path: string
66
- ) {
67
- if (!mount) return `/${path.replace(/^\/+/, '')}`;
68
- return `/service/openxiangda-app-api/v2/${encodeURIComponent(
69
- mount.appCode
70
- )}/${encodeURIComponent(mount.environmentKey)}/${path.replace(/^\/+/, '')}`;
71
- }
72
-
73
- export function applicationApiPath(path: string) {
74
- return resolveApplicationApiPath(runtimeMount(), path);
75
- }
@@ -1,227 +0,0 @@
1
- import { Alert, Button, Result, Space, Spin, Typography } from 'antd';
2
- import {
3
- createContext,
4
- useCallback,
5
- useContext,
6
- useEffect,
7
- useMemo,
8
- useState,
9
- } from 'react';
10
- import {
11
- loadRuntimeAuthorization,
12
- subscribeRoleSessionInvalidation,
13
- switchRuntimeRole,
14
- type RuntimeAuthorization,
15
- type RuntimeIdentity,
16
- } from './platform-client';
17
- import { RoleSubjectSelect } from './RoleSubjectSelect';
18
- import { runtimeMount } from './runtime-meta';
19
-
20
- interface RuntimeValue {
21
- identity: RuntimeIdentity;
22
- authorization: RuntimeAuthorization;
23
- identityEpoch: number;
24
- switchingRole: boolean;
25
- hasCapability(code: string): boolean;
26
- switchRole(targetRoleSubjectKey: string): Promise<void>;
27
- }
28
-
29
- const RuntimeContext = createContext<RuntimeValue | null>(null);
30
-
31
- export function RuntimeBoundary({ children }: { children: React.ReactNode }) {
32
- const [authorization, setAuthorization] = useState<RuntimeAuthorization>();
33
- const [error, setError] = useState<Error>();
34
- const [attempt, setAttempt] = useState(0);
35
- const [identityEpoch, setIdentityEpoch] = useState(0);
36
- const [switchingRole, setSwitchingRole] = useState(false);
37
-
38
- useEffect(
39
- () =>
40
- subscribeRoleSessionInvalidation(() => {
41
- setAuthorization(undefined);
42
- setError(undefined);
43
- setAttempt(value => value + 1);
44
- }),
45
- []
46
- );
47
-
48
- useEffect(() => {
49
- let active = true;
50
- setAuthorization(undefined);
51
- setError(undefined);
52
- void loadRuntimeAuthorization().then(
53
- value => {
54
- if (!active) return;
55
- setAuthorization(value);
56
- setIdentityEpoch(epoch => epoch + 1);
57
- },
58
- reason =>
59
- active &&
60
- setError(reason instanceof Error ? reason : new Error(String(reason)))
61
- );
62
- return () => {
63
- active = false;
64
- };
65
- }, [attempt]);
66
-
67
- const switchRole = useCallback(
68
- async (targetRoleSubjectKey: string) => {
69
- if (!authorization?.roleContext) {
70
- throw new Error('OPENXIANGDA_ROLE_SWITCH_UNAVAILABLE');
71
- }
72
- setSwitchingRole(true);
73
- try {
74
- const next = await switchRuntimeRole(
75
- targetRoleSubjectKey,
76
- authorization.roleContext.roleSession?.id || null
77
- );
78
- setAuthorization(next);
79
- setIdentityEpoch(epoch => epoch + 1);
80
- } catch (reason) {
81
- setAuthorization(undefined);
82
- setAttempt(value => value + 1);
83
- throw reason;
84
- } finally {
85
- setSwitchingRole(false);
86
- }
87
- },
88
- [authorization]
89
- );
90
-
91
- const identity = authorization?.identity || null;
92
- const value = useMemo<RuntimeValue | null>(
93
- () =>
94
- identity && authorization
95
- ? {
96
- identity,
97
- authorization,
98
- identityEpoch,
99
- switchingRole,
100
- hasCapability: code =>
101
- identity.isAppSuperAdmin ||
102
- identity.capabilityCodes.includes(code),
103
- switchRole,
104
- }
105
- : null,
106
- [authorization, identity, identityEpoch, switchRole, switchingRole]
107
- );
108
- const production =
109
- identity?.environment.key === 'production' ||
110
- runtimeMount()?.environmentKey === 'production';
111
-
112
- return (
113
- <>
114
- {production && (
115
- <Alert
116
- banner
117
- showIcon
118
- type="error"
119
- data-testid="production-data-warning"
120
- title="正在使用正式数据:所有新增、编辑和删除都会影响生产环境"
121
- />
122
- )}
123
- {error ? (
124
- <Result
125
- status="error"
126
- title="无法读取当前应用角色"
127
- subTitle={error.message}
128
- extra={
129
- <Button onClick={() => setAttempt(value => value + 1)}>
130
- 重试
131
- </Button>
132
- }
133
- />
134
- ) : authorization?.state === 'selection_required' &&
135
- authorization.roleContext ? (
136
- <InitialRoleSelection
137
- authorization={authorization}
138
- switching={switchingRole}
139
- onSelect={switchRole}
140
- />
141
- ) : authorization?.state === 'unassigned' ? (
142
- <Result
143
- status="403"
144
- title="尚未分配应用角色"
145
- subTitle="请联系应用管理员分配当前环境中的应用角色后重试。"
146
- extra={
147
- <Button onClick={() => setAttempt(value => value + 1)}>
148
- 重新检查
149
- </Button>
150
- }
151
- />
152
- ) : value ? (
153
- <RuntimeContext.Provider
154
- key={`${value.identity.identityScope}:${identityEpoch}`}
155
- value={value}
156
- >
157
- {children}
158
- </RuntimeContext.Provider>
159
- ) : (
160
- <div className="oxa-loading">
161
- <Spin description="正在读取当前应用角色与权限" />
162
- </div>
163
- )}
164
- </>
165
- );
166
- }
167
-
168
- function InitialRoleSelection({
169
- authorization,
170
- switching,
171
- onSelect,
172
- }: {
173
- authorization: RuntimeAuthorization;
174
- switching: boolean;
175
- onSelect: (targetRoleSubjectKey: string) => Promise<void>;
176
- }) {
177
- const [selected, setSelected] = useState('');
178
- const [selectionError, setSelectionError] = useState('');
179
- const roleSubjectPage = authorization.roleContext?.roleSubjectPage;
180
- const submit = async () => {
181
- if (!selected) return;
182
- setSelectionError('');
183
- try {
184
- await onSelect(selected);
185
- } catch (reason) {
186
- setSelectionError(
187
- reason instanceof Error ? reason.message : '应用角色切换失败'
188
- );
189
- }
190
- };
191
- return (
192
- <Result
193
- status="info"
194
- title="选择当前应用角色"
195
- subTitle="本次应用访问只使用一个角色;进入应用后仍可从个人菜单切换。"
196
- extra={
197
- <Space align="start" direction="vertical" size="middle">
198
- {roleSubjectPage && (
199
- <RoleSubjectSelect
200
- disabled={switching}
201
- initialPage={roleSubjectPage}
202
- onChange={setSelected}
203
- value={selected}
204
- />
205
- )}
206
- {selectionError && (
207
- <Typography.Text type="danger">{selectionError}</Typography.Text>
208
- )}
209
- <Button
210
- disabled={!selected}
211
- loading={switching}
212
- onClick={() => void submit()}
213
- type="primary"
214
- >
215
- 进入应用
216
- </Button>
217
- </Space>
218
- }
219
- />
220
- );
221
- }
222
-
223
- export function useRuntime() {
224
- const value = useContext(RuntimeContext);
225
- if (!value) throw new Error('OPENXIANGDA_RUNTIME_NOT_READY');
226
- return value;
227
- }