@77-zhike/app-sdk 0.2.0-d7b1.5 → 0.2.0-d7b1.7
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 +192 -0
- package/package.json +1 -1
- package/src/cli/project-init.js +17 -1
- package/src/client/destinations.d.ts +25 -0
- package/src/client/destinations.js +70 -0
- package/src/client/external-url.js +9 -0
- package/src/client/index.d.ts +191 -3
- package/src/client/index.js +46 -3
- package/src/client/interactions.d.ts +9 -3
- package/src/client/interactions.js +32 -2
- package/src/client/points.generated.js +48 -0
- package/src/forms/components.generated.js +2 -2
- package/src/forms/factory.js +14 -5
- package/src/forms/index.d.ts +63 -3
- package/src/forms/index.js +2 -0
- package/src/forms/options.d.ts +7 -2
- package/src/forms/options.js +8 -1
- package/src/forms/record-options.d.ts +28 -0
- package/src/forms/record-options.js +92 -0
- package/src/forms/schema.d.ts +50 -5
- package/src/forms/schema.js +105 -0
- package/src/forms/state.d.ts +28 -0
- package/src/forms/state.js +92 -0
- package/src/forms/validation.d.ts +9 -0
- package/src/forms/validation.js +83 -0
- package/src/forms/values.d.ts +5 -0
- package/src/forms/values.js +38 -0
- package/src/server/index.d.ts +15 -0
- package/src/server/index.js +20 -0
- package/src/toolchain/real-build.js +29 -3
- package/src/toolchain/source-discovery.js +202 -16
- package/src/ui/components.generated.js +3 -1
- package/src/ui/factory.js +50 -3
- package/src/ui/index.d.ts +157 -8
- package/src/ui/index.js +1 -1
- package/src/ui/validation.js +38 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { assertJsonValue } from '../ui/validation.js';
|
|
2
|
+
import { isDateTime, isPlainDate } from './values.js';
|
|
3
|
+
import { assertRecordRef, recordObjectsForField } from './record-options.js';
|
|
4
|
+
const fail = () => { throw new Error('runtime_form_state_invalid'); };
|
|
5
|
+
const only = (value, keys) => {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !keys.includes(key))) fail();
|
|
7
|
+
};
|
|
8
|
+
export function selectedNames(selection, fields) {
|
|
9
|
+
return selection === true ? Object.keys(fields) : Array.isArray(selection) ? selection : [];
|
|
10
|
+
}
|
|
11
|
+
export function assertFormStateSelection(selection, fields) {
|
|
12
|
+
only(selection, ['values', 'errors', 'submitting', 'validating', 'validatingFields', 'formError']);
|
|
13
|
+
for (const kind of ['values', 'errors', 'validatingFields']) {
|
|
14
|
+
const names = selection[kind];
|
|
15
|
+
if (names === undefined || names === true) continue;
|
|
16
|
+
if (!Array.isArray(names) || names.length > 30 || new Set(names).size !== names.length || names.some(name => typeof name !== 'string' || !Object.hasOwn(fields, name))) fail();
|
|
17
|
+
}
|
|
18
|
+
for (const key of ['submitting', 'validating', 'formError']) if (selection[key] !== undefined && typeof selection[key] !== 'boolean') fail();
|
|
19
|
+
return selection;
|
|
20
|
+
}
|
|
21
|
+
function assertValue(value, field) {
|
|
22
|
+
switch (field.type) {
|
|
23
|
+
case 'boolean': if (typeof value !== 'boolean') fail(); break;
|
|
24
|
+
case 'number': if (value !== null && (typeof value !== 'number' || !Number.isFinite(value))) fail(); break;
|
|
25
|
+
case 'plainDate': if (value !== null && !isPlainDate(value)) fail(); break;
|
|
26
|
+
case 'dateTime': if (value !== null && !isDateTime(value)) fail(); break;
|
|
27
|
+
case 'record': if (value !== null) assertRecordRef(value, recordObjectsForField(field)); break;
|
|
28
|
+
default: if (typeof value !== 'string' || value.length > 10000) fail();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function assertFormStateSnapshot(snapshot, formId, fields, selection) {
|
|
32
|
+
assertJsonValue(snapshot);
|
|
33
|
+
assertFormStateSelection(selection, fields);
|
|
34
|
+
const keys = ['formId', 'revision', ...(selection.values !== undefined ? ['values'] : []), ...(selection.errors !== undefined ? ['errors'] : []), ...['submitting', 'validating', 'formError'].filter(key => selection[key]), ...(selection.validatingFields !== undefined ? ['validatingFields'] : [])];
|
|
35
|
+
only(snapshot, keys);
|
|
36
|
+
if (Object.keys(snapshot).length !== keys.length || snapshot.formId !== formId || !Number.isSafeInteger(snapshot.revision) || snapshot.revision < 1) fail();
|
|
37
|
+
if (selection.values !== undefined) {
|
|
38
|
+
const names = selectedNames(selection.values, fields);
|
|
39
|
+
only(snapshot.values, names);
|
|
40
|
+
if (Object.keys(snapshot.values).length !== names.length) fail();
|
|
41
|
+
for (const name of names) assertValue(snapshot.values[name], fields[name]);
|
|
42
|
+
}
|
|
43
|
+
if (selection.errors !== undefined) {
|
|
44
|
+
only(snapshot.errors, selectedNames(selection.errors, fields));
|
|
45
|
+
for (const value of Object.values(snapshot.errors)) if (typeof value !== 'string' || !value || value.length > 4096) fail();
|
|
46
|
+
}
|
|
47
|
+
for (const key of ['submitting', 'validating']) if (selection[key] && typeof snapshot[key] !== 'boolean') fail();
|
|
48
|
+
if (selection.formError && snapshot.formError !== null && (typeof snapshot.formError !== 'string' || !snapshot.formError || snapshot.formError.length > 4096)) fail();
|
|
49
|
+
if (selection.validatingFields !== undefined) {
|
|
50
|
+
only(snapshot.validatingFields, selectedNames(selection.validatingFields, fields));
|
|
51
|
+
if (Object.values(snapshot.validatingFields).some(value => typeof value !== 'boolean')) fail();
|
|
52
|
+
}
|
|
53
|
+
return snapshot;
|
|
54
|
+
}
|
|
55
|
+
export function freezeFormState(value) {
|
|
56
|
+
if (value && typeof value === 'object') { Object.values(value).forEach(freezeFormState); Object.freeze(value); }
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
export function initialFormState(formId, fields, initialValues, selection) {
|
|
60
|
+
const snapshot = { formId, revision: 1 };
|
|
61
|
+
if (selection.values !== undefined) snapshot.values = Object.fromEntries(selectedNames(selection.values, fields).map(name => {
|
|
62
|
+
const value = initialValues[name];
|
|
63
|
+
return [name, fields[name].type === 'dateTime' && value ? new Date(value).toISOString() : value && typeof value === 'object' ? { ...value } : value];
|
|
64
|
+
}));
|
|
65
|
+
if (selection.errors !== undefined) snapshot.errors = {};
|
|
66
|
+
for (const key of ['submitting', 'validating']) if (selection[key]) snapshot[key] = false;
|
|
67
|
+
if (selection.formError) snapshot.formError = null;
|
|
68
|
+
if (selection.validatingFields !== undefined) snapshot.validatingFields = {};
|
|
69
|
+
assertFormStateSnapshot(snapshot, formId, fields, selection);
|
|
70
|
+
return snapshot;
|
|
71
|
+
}
|
|
72
|
+
export function createWithState({ createElement, Fragment, useMemo, useState, useRef }, formId, schema, initialValues) {
|
|
73
|
+
function Observer({ selection, children }) {
|
|
74
|
+
const revision = useRef(0);
|
|
75
|
+
const [snapshot, setSnapshot] = useState(() => initialFormState(formId, schema.fields, initialValues, selection));
|
|
76
|
+
const onState = useMemo(() => next => {
|
|
77
|
+
assertFormStateSnapshot(next, formId, schema.fields, selection);
|
|
78
|
+
if (next.revision <= revision.current) return;
|
|
79
|
+
revision.current = next.revision;
|
|
80
|
+
setSnapshot(freezeFormState(next));
|
|
81
|
+
}, []);
|
|
82
|
+
const { formId: _, revision: _revision, ...state } = snapshot;
|
|
83
|
+
return createElement(Fragment, null,
|
|
84
|
+
createElement('FormStateObserver', { formId, selection, onState }),
|
|
85
|
+
children(freezeFormState(state)));
|
|
86
|
+
}
|
|
87
|
+
return function WithState({ selection, children }) {
|
|
88
|
+
assertFormStateSelection(selection, schema.fields);
|
|
89
|
+
if (typeof children !== 'function') fail();
|
|
90
|
+
return createElement(Observer, { key: JSON.stringify(selection), selection, children });
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const VALIDATION_TIMEOUT_MS: 10000;
|
|
2
|
+
export interface ValidationMetadata { fields: string[]; form: boolean; mode: 'onBlur' | 'onChange' | 'onSubmit'; }
|
|
3
|
+
export interface ValidationRequest {
|
|
4
|
+
formId: string; runId: number; reason: 'blur' | 'change' | 'submit';
|
|
5
|
+
values: Record<string, string | boolean | number | import("./record-options.js").RecordRef | null>; fields: string[]; includeForm: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ValidationResponse { formId: string; runId: number; fieldErrors: Record<string, string>; formError?: string; }
|
|
8
|
+
export function assertValidationMetadata(value: unknown, fields: Record<string, unknown>): ValidationMetadata;
|
|
9
|
+
export function assertValidationResponse(value: unknown, request: Pick<ValidationRequest, 'formId' | 'runId'>, fields: Record<string, unknown>): ValidationResponse;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { freezeFormState } from './state.js';
|
|
2
|
+
import { isDateTime, isPlainDate } from './values.js';
|
|
3
|
+
import { assertRecordRef, recordObjectsForField } from './record-options.js';
|
|
4
|
+
|
|
5
|
+
export const VALIDATION_TIMEOUT_MS = 10000;
|
|
6
|
+
const fail = () => { throw new Error('runtime_form_validation_invalid'); };
|
|
7
|
+
const only = (value, keys) => {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).some(key => !keys.includes(key))) fail();
|
|
9
|
+
};
|
|
10
|
+
const validMessage = value => typeof value === 'string' && value.trim() && value.length <= 300;
|
|
11
|
+
export function assertValidationMetadata(value, fields) {
|
|
12
|
+
only(value, ['fields', 'form', 'mode']);
|
|
13
|
+
if (!Array.isArray(value.fields) || value.fields.length > 30 || new Set(value.fields).size !== value.fields.length
|
|
14
|
+
|| value.fields.some(name => typeof name !== 'string' || !Object.hasOwn(fields, name))
|
|
15
|
+
|| typeof value.form !== 'boolean' || !['onBlur', 'onChange', 'onSubmit'].includes(value.mode)) fail();
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
export function assertValidationResponse(value, request, fields) {
|
|
19
|
+
only(value, ['formId', 'runId', 'fieldErrors', 'formError']);
|
|
20
|
+
if (value.formId !== request.formId || value.runId !== request.runId) fail();
|
|
21
|
+
only(value.fieldErrors, Object.keys(fields));
|
|
22
|
+
if (Object.values(value.fieldErrors).some(error => !validMessage(error)) || value.formError !== undefined && !validMessage(value.formError)) fail();
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function assertRequest(request, formId, fields, validators, validate) {
|
|
26
|
+
only(request, ['formId', 'runId', 'reason', 'values', 'fields', 'includeForm']);
|
|
27
|
+
if (request.formId !== formId || !Number.isSafeInteger(request.runId) || request.runId < 1
|
|
28
|
+
|| !['blur', 'change', 'submit'].includes(request.reason) || typeof request.includeForm !== 'boolean'
|
|
29
|
+
|| request.includeForm && !validate || !Array.isArray(request.fields) || new Set(request.fields).size !== request.fields.length
|
|
30
|
+
|| request.fields.some(name => !Object.hasOwn(validators, name))) fail();
|
|
31
|
+
only(request.values, Object.keys(fields));
|
|
32
|
+
if (Object.keys(request.values).length !== Object.keys(fields).length) fail();
|
|
33
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
34
|
+
const value = request.values[name];
|
|
35
|
+
if (field.type === 'boolean') { if (typeof value !== 'boolean') fail(); }
|
|
36
|
+
else if (field.type === 'record') { if (value !== null) assertRecordRef(value, recordObjectsForField(field)); }
|
|
37
|
+
else if (field.type === 'number') { if (value !== null && (typeof value !== 'number' || !Number.isFinite(value))) fail(); }
|
|
38
|
+
else if (['plainDate', 'dateTime'].includes(field.type)) { if (value !== null && !(field.type === 'plainDate' ? isPlainDate(value) : isDateTime(value))) fail(); }
|
|
39
|
+
else if (typeof value !== 'string' || value.length > 10000) fail();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function createValidationCallback(formId, fields, validators, validate) {
|
|
43
|
+
let busy = false;
|
|
44
|
+
return async input => {
|
|
45
|
+
assertRequest(input, formId, fields, validators, validate);
|
|
46
|
+
if (busy) fail();
|
|
47
|
+
busy = true;
|
|
48
|
+
const request = freezeFormState(structuredClone(input));
|
|
49
|
+
let active = true, timer, position = 0;
|
|
50
|
+
const fieldErrors = {};
|
|
51
|
+
const runField = async () => {
|
|
52
|
+
while (active && position < request.fields.length) {
|
|
53
|
+
const name = request.fields[position++], value = request.values[name];
|
|
54
|
+
if (!fields[name].required && (value === '' || value === null)) continue;
|
|
55
|
+
for (const validator of validators[name]) {
|
|
56
|
+
if (!active) return;
|
|
57
|
+
const error = await validator(value);
|
|
58
|
+
if (!active) return;
|
|
59
|
+
if (error !== undefined) { if (!validMessage(error)) fail(); fieldErrors[name] = error; break; }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
return await Promise.race([
|
|
65
|
+
(async () => {
|
|
66
|
+
await Promise.all([runField(), runField()]);
|
|
67
|
+
if (!active) return;
|
|
68
|
+
const result = request.includeForm ? await validate(request.values) : undefined;
|
|
69
|
+
if (!active) return;
|
|
70
|
+
if (result !== undefined) { only(result, ['fieldErrors', 'formError']); }
|
|
71
|
+
// 字段 custom 错误优先于跨字段错误。
|
|
72
|
+
const response = { formId, runId: request.runId, fieldErrors: { ...result?.fieldErrors, ...fieldErrors }, ...(result?.formError === undefined ? {} : { formError: result.formError }) };
|
|
73
|
+
if (result?.fieldErrors !== undefined) {
|
|
74
|
+
only(result.fieldErrors, Object.keys(fields));
|
|
75
|
+
if (Object.values(result.fieldErrors).some(error => !validMessage(error))) fail();
|
|
76
|
+
}
|
|
77
|
+
return assertValidationResponse(response, request, fields);
|
|
78
|
+
})(),
|
|
79
|
+
new Promise((_, reject) => { timer = setTimeout(() => { active = false; reject(new Error('runtime_form_validation_timeout')); }, VALIDATION_TIMEOUT_MS); }),
|
|
80
|
+
]);
|
|
81
|
+
} finally { active = false; busy = false; clearTimeout(timer); }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function isPlainDate(value: unknown): value is string;
|
|
2
|
+
export function isDateTime(value: unknown): value is string;
|
|
3
|
+
export function parseNumberDraft(value: unknown): { value: number | null; error?: string };
|
|
4
|
+
export function instantToLocal(value: string | null): string;
|
|
5
|
+
export function localToInstant(value: string): { value: string | null; error?: string };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 平台值格式,不包含业务字段规则或缺省时区。 */
|
|
2
|
+
export function isPlainDate(value) {
|
|
3
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
4
|
+
const [year, month, day] = value.split('-').map(Number);
|
|
5
|
+
const date = new Date(0);
|
|
6
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
7
|
+
return year > 0 && date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
8
|
+
}
|
|
9
|
+
export function isDateTime(value) {
|
|
10
|
+
if (typeof value !== 'string') return false;
|
|
11
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?(Z|[+-]\d{2}:\d{2})$/.exec(value);
|
|
12
|
+
return Boolean(match && isPlainDate(match[1]) && +match[2] < 24 && +match[3] < 60 && +(match[4] ?? 0) < 60 && Number.isFinite(Date.parse(value)));
|
|
13
|
+
}
|
|
14
|
+
export function parseNumberDraft(value) {
|
|
15
|
+
if (value === null || value === '') return { value: null };
|
|
16
|
+
if (typeof value === 'number') return Number.isFinite(value) ? { value } : { value: null, error: '请输入有效数字' };
|
|
17
|
+
if (typeof value !== 'string' || value.length > 512 || !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) return { value: null, error: '请输入有效数字' };
|
|
18
|
+
const number = Number(value);
|
|
19
|
+
return Number.isFinite(number) ? { value: number } : { value: null, error: '请输入有效数字' };
|
|
20
|
+
}
|
|
21
|
+
const pad = value => String(value).padStart(2, '0');
|
|
22
|
+
export function instantToLocal(value) {
|
|
23
|
+
if (!value) return '';
|
|
24
|
+
if (!isDateTime(value)) return value;
|
|
25
|
+
const date = new Date(value);
|
|
26
|
+
return `${String(date.getFullYear()).padStart(4, '0')}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
27
|
+
}
|
|
28
|
+
export function localToInstant(value) {
|
|
29
|
+
if (!value) return { value: null };
|
|
30
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})$/.exec(value);
|
|
31
|
+
if (!match || !isPlainDate(match[1]) || +match[2] > 23 || +match[3] > 59) return { value: null, error: '请输入有效日期时间' };
|
|
32
|
+
const wall = new Date(value);
|
|
33
|
+
const utc = new Date(value + ':00.000Z').getTime();
|
|
34
|
+
const offsets = new Set([-36, 0, 36].map(hours => new Date(wall.getTime() + hours * 3600000).getTimezoneOffset()));
|
|
35
|
+
const candidates = [...offsets].map(offset => new Date(utc + offset * 60000).toISOString()).filter(instant => instantToLocal(instant) === value);
|
|
36
|
+
if (candidates.length !== 1) return { value: null, error: '该本地时间不存在或有歧义,请选择其他时间' };
|
|
37
|
+
return { value: candidates[0] };
|
|
38
|
+
}
|
package/src/server/index.d.ts
CHANGED
|
@@ -221,6 +221,21 @@ export interface WorkspaceConnection {
|
|
|
221
221
|
/** 同步读取当前执行的连接快照;未连接或暂不可用时抛出安全错误,不在 getter 内刷新。 */
|
|
222
222
|
export function getWorkspaceConnection(): Readonly<WorkspaceConnection>;
|
|
223
223
|
|
|
224
|
+
/** 当前 Server Function 调用者在当前 Workspace 内的成员快照。 */
|
|
225
|
+
export interface CurrentUser {
|
|
226
|
+
/** Workspace 成员 ID;不是平台全局 User ID。 */
|
|
227
|
+
workspaceMemberId: string;
|
|
228
|
+
/** 当前 Workspace 中的显示名称。 */
|
|
229
|
+
displayName: string;
|
|
230
|
+
/** 当前 Workspace 中的主邮箱。 */
|
|
231
|
+
primaryEmailAddress: string | null;
|
|
232
|
+
/** 当前平台用户头像;未绑定平台用户时为空。 */
|
|
233
|
+
avatarUrl: string | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** 同步读取当前调用者;Lifecycle、Webhook 等无人触发入口会抛出 current_user_unavailable。 */
|
|
237
|
+
export function getCurrentUser(): Readonly<CurrentUser>;
|
|
238
|
+
|
|
224
239
|
/** Server Function 的一期函数合同。 */
|
|
225
240
|
export type ServerFunction<TArgs extends JsonValue[] = JsonValue[], TResult extends JsonValue = JsonValue> =
|
|
226
241
|
(...args: TArgs) => Promise<TResult>;
|
package/src/server/index.js
CHANGED
|
@@ -153,6 +153,26 @@ export function getWorkspaceConnection() {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
export function getCurrentUser() {
|
|
157
|
+
const current = globalThis.ZHIKE_CURRENT_USER;
|
|
158
|
+
if (
|
|
159
|
+
!current ||
|
|
160
|
+
typeof current !== "object" ||
|
|
161
|
+
typeof current.workspaceMemberId !== "string" ||
|
|
162
|
+
typeof current.displayName !== "string"
|
|
163
|
+
) {
|
|
164
|
+
const error = new Error("current_user_unavailable");
|
|
165
|
+
error.code = "current_user_unavailable";
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
return Object.freeze({
|
|
169
|
+
workspaceMemberId: current.workspaceMemberId,
|
|
170
|
+
displayName: current.displayName,
|
|
171
|
+
primaryEmailAddress: typeof current.primaryEmailAddress === "string" ? current.primaryEmailAddress : null,
|
|
172
|
+
avatarUrl: typeof current.avatarUrl === "string" ? current.avatarUrl : null,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
// 仅兼容已构建的旧 Bundle;新源码入口由 .server.ts 的默认 async 函数确定。
|
|
157
177
|
export function defineServerFunction(handler) {
|
|
158
178
|
if (typeof handler !== "function") {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { componentTypes } from '../ui/components.generated.js';
|
|
1
2
|
import { createRequire } from "node:module";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
@@ -245,6 +246,24 @@ function createSdkBuildPlugin(projectDirectory, sources, target) {
|
|
|
245
246
|
return {
|
|
246
247
|
name: "zhike-app-sdk-runtime",
|
|
247
248
|
setup(buildApi) {
|
|
249
|
+
if (target === "remote-client") {
|
|
250
|
+
// 新构建固化 authoring 默认值;Guest 仍为不可变旧 Bundle 提供兼容模块。
|
|
251
|
+
buildApi.onResolve({ filter: /^@77-zhike\/app-sdk\/ui$/ }, args => args.namespace === "zhike-ui-authoring"
|
|
252
|
+
? { path: args.path, external: true }
|
|
253
|
+
: { path: args.path, namespace: "zhike-ui-authoring" });
|
|
254
|
+
buildApi.onLoad({ filter: /.*/, namespace: "zhike-ui-authoring" }, () => ({ loader: "js", contents: `
|
|
255
|
+
import * as ui from "@77-zhike/app-sdk/ui";
|
|
256
|
+
import { createElement } from "react";
|
|
257
|
+
export const TextBlock = props => createElement(ui.TextBlock, { ...props, align: props.align ?? "left" });
|
|
258
|
+
export const Badge = props => {
|
|
259
|
+
if (props.tone !== undefined) throw new Error("Badge.tone 已移除,请使用 StatusBadge");
|
|
260
|
+
return createElement(ui.Badge, props);
|
|
261
|
+
};
|
|
262
|
+
export const TableCell = Object.defineProperties(props => createElement(ui.TableCell, props), { Avatar: { get: () => ui.Avatar }, Badge: { value: Badge } });
|
|
263
|
+
export const Table = Object.defineProperties(props => createElement(ui.Table, props), { Header: { get: () => ui.Table.Header }, HeaderCell: { get: () => ui.Table.HeaderCell }, Body: { get: () => ui.Table.Body }, Row: { get: () => ui.Table.Row }, Cell: { value: TableCell } });
|
|
264
|
+
export const { ${[...componentTypes.filter(type => !["TextBlock", "Badge", "Table", "TableCell"].includes(type)), "Typography", "Widget"].join(", ")} } = ui;
|
|
265
|
+
` }));
|
|
266
|
+
}
|
|
248
267
|
if (target !== "remote-client") buildApi.onResolve({ filter: /^@77-zhike\/app-sdk\/(client|server)$/ }, (args) => ({
|
|
249
268
|
external: target === "server" && args.path.endsWith("/server"),
|
|
250
269
|
path: args.path,
|
|
@@ -254,7 +273,10 @@ function createSdkBuildPlugin(projectDirectory, sources, target) {
|
|
|
254
273
|
}));
|
|
255
274
|
buildApi.onLoad({ filter: /.*/, namespace: "zhike-sdk-runtime" }, (args) => {
|
|
256
275
|
if (args.path.endsWith("/client")) {
|
|
257
|
-
return {
|
|
276
|
+
return {
|
|
277
|
+
contents: "export const defineFrontendExtension = (definition) => definition; export const definePage = (definition) => ({ ...definition, type: 'page' });",
|
|
278
|
+
loader: "js",
|
|
279
|
+
};
|
|
258
280
|
}
|
|
259
281
|
return { contents: "export const defineLifecycleEvent = (definition) => definition; export const defineServerFunction = (handler) => handler; export const defineWebhookHandler = (definition) => definition; export const createWebhookHandler = async () => { throw new Error('webhook_handler_runtime_unavailable'); }; export const listWebhookHandlers = async () => { throw new Error('webhook_handler_runtime_unavailable'); }; export const deleteWebhookHandler = async () => { throw new Error('webhook_handler_runtime_unavailable'); };", loader: "js" };
|
|
260
282
|
});
|
|
@@ -293,7 +315,7 @@ function createManifest({ clientBytes, serverBytes, sources }) {
|
|
|
293
315
|
sdkAbiVersion: "2",
|
|
294
316
|
toolchainVersion: TOOLING_VERSION,
|
|
295
317
|
...(sources.frontendExtensions.length > 0
|
|
296
|
-
? { clientRuntime: { runtimeVersion: "remote-react.
|
|
318
|
+
? { clientRuntime: { runtimeVersion: "remote-react.v3" } } : {}),
|
|
297
319
|
artifacts: {
|
|
298
320
|
...(clientBytes
|
|
299
321
|
? {
|
|
@@ -316,7 +338,7 @@ function createManifest({ clientBytes, serverBytes, sources }) {
|
|
|
316
338
|
}
|
|
317
339
|
: {}),
|
|
318
340
|
},
|
|
319
|
-
frontendExtensions: sources.frontendExtensions.map(({ key, type, entry, label, icon, objects }) => ({
|
|
341
|
+
frontendExtensions: sources.frontendExtensions.map(({ key, type, entry, label, icon, objects, name, order, variant, showInNavigation }) => ({
|
|
320
342
|
artifact: "client",
|
|
321
343
|
entry,
|
|
322
344
|
key,
|
|
@@ -324,6 +346,10 @@ function createManifest({ clientBytes, serverBytes, sources }) {
|
|
|
324
346
|
...(label === undefined ? {} : { label }),
|
|
325
347
|
...(icon === undefined ? {} : { icon }),
|
|
326
348
|
...(objects === undefined ? {} : { objects }),
|
|
349
|
+
...(name === undefined ? {} : { name }),
|
|
350
|
+
...(order === undefined ? {} : { order }),
|
|
351
|
+
...(variant === undefined ? {} : { variant }),
|
|
352
|
+
...(showInNavigation === undefined ? {} : { showInNavigation }),
|
|
327
353
|
})),
|
|
328
354
|
lifecycleEvents: sources.lifecycleEvents.map(({ event, key }) => ({
|
|
329
355
|
artifact: "server",
|