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.
- package/README.md +2 -2
- package/bin/run.js +1 -0
- package/dist/commands/skill.d.ts +26 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +43 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/create-workspace.d.ts.map +1 -1
- package/dist/create-workspace.js +3 -3
- package/dist/create-workspace.js.map +1 -1
- package/dist/toolchain-capsule.d.ts.map +1 -1
- package/dist/toolchain-capsule.js +4 -8
- package/dist/toolchain-capsule.js.map +1 -1
- package/package.json +6 -7
- package/template/AGENTS.md +2 -1
- package/template/README.md +5 -5
- package/template/apps/server/package.json +1 -1
- package/template/apps/server/src/app.module.ts +14 -1
- package/template/apps/server/src/main.ts +1 -1
- package/template/apps/server/test/smoke.test.ts +3 -1
- package/template/apps/web/e2e/client-fixture.ts +5 -0
- package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
- package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
- package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
- package/template/apps/web/e2e/resources.spec.ts +20 -181
- package/template/apps/web/field-protocol.e2e.html +3 -0
- package/template/apps/web/package.json +2 -5
- package/template/apps/web/src/main.tsx +12 -60
- package/template/apps/web/test/contracts.test.ts +52 -104
- package/template/openxiangda.config.ts +2 -2
- package/template/package.json +1 -3
- package/template/packages/contracts/src/generated.ts +27 -0
- package/template/scripts/verify-template-budget.mjs +2 -2
- package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
- package/template/apps/web/src/FilePreviewPage.tsx +0 -324
- package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
- package/template/apps/web/src/Shell.tsx +0 -445
- package/template/apps/web/src/appearance.tsx +0 -147
- package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
- package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
- package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
- package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
- package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
- package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
- package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
- package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
- package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
- package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
- package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
- package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
- package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
- package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
- package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
- package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
- package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
- package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
- package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
- package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
- package/template/apps/web/src/components/resource/resource-import.ts +0 -205
- package/template/apps/web/src/data-provider.ts +0 -72
- package/template/apps/web/src/platform-client.ts +0 -708
- package/template/apps/web/src/runtime-meta.ts +0 -75
- package/template/apps/web/src/runtime.tsx +0 -227
- package/template/apps/web/src/styles.css +0 -1941
- package/template/apps/web/test/address-field.test.ts +0 -66
- package/template/apps/web/test/cascade-value.test.ts +0 -68
- package/template/apps/web/test/directory-value.test.ts +0 -55
- package/template/apps/web/test/field-bounds.test.ts +0 -17
- package/template/apps/web/test/field-form-codec.test.ts +0 -85
- package/template/apps/web/test/location-field.test.ts +0 -83
- package/template/apps/web/test/resource-import.test.ts +0 -143
- package/template/apps/web/test/resource-query.test.ts +0 -144
- package/template/apps/web/test/rich-json-field.test.ts +0 -53
- package/template/apps/web/test/signature-serial-field.test.ts +0 -40
- 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
|
-
}
|