@77-zhike/app-sdk 0.2.0-d7b1.2
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/package.json +57 -0
- package/src/bin.js +15 -0
- package/src/cli/api-client.js +245 -0
- package/src/cli/browser-auth.js +212 -0
- package/src/cli/credential-store.js +101 -0
- package/src/cli/development-app.js +77 -0
- package/src/cli/development-logs.js +180 -0
- package/src/cli/development-watch.js +205 -0
- package/src/cli/index.js +253 -0
- package/src/cli/local-commands.js +78 -0
- package/src/cli/project-init.js +371 -0
- package/src/cli/upload-bundle.js +73 -0
- package/src/cli/validate-bundle.js +36 -0
- package/src/client/index.d.ts +52 -0
- package/src/client/index.js +22 -0
- package/src/client/interactions.d.ts +8 -0
- package/src/client/interactions.js +23 -0
- package/src/client/points.generated.js +19 -0
- package/src/config.js +64 -0
- package/src/development-workspace.js +154 -0
- package/src/errors.js +20 -0
- package/src/forms/components.generated.js +4 -0
- package/src/forms/factory.js +27 -0
- package/src/forms/index.d.ts +54 -0
- package/src/forms/index.js +3 -0
- package/src/forms/normalize-options.js +19 -0
- package/src/forms/options.d.ts +25 -0
- package/src/forms/options.js +48 -0
- package/src/forms/schema.d.ts +18 -0
- package/src/index.d.ts +13 -0
- package/src/index.js +2 -0
- package/src/project-binding.js +84 -0
- package/src/server/index.d.ts +268 -0
- package/src/server/index.js +201 -0
- package/src/toolchain/canonical-json.js +17 -0
- package/src/toolchain/index.js +103 -0
- package/src/toolchain/real-build.js +371 -0
- package/src/toolchain/source-discovery.js +710 -0
- package/src/toolchain/transport-tar.js +168 -0
- package/src/toolchain/webhook-response.js +50 -0
- package/src/ui/components.generated.js +2 -0
- package/src/ui/factory.js +19 -0
- package/src/ui/icons.generated.js +2 -0
- package/src/ui/index.d.ts +111 -0
- package/src/ui/index.js +4 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { JsonValue } from "../index.js";
|
|
2
|
+
|
|
3
|
+
/** 当前执行的 App API Token;由可信 Runtime Module 在服务端模块求值前注入。 */
|
|
4
|
+
export const ZHIKE_API_TOKEN: string;
|
|
5
|
+
|
|
6
|
+
/** 由调用方选择稳定操作身份;SDK 不生成 Key 或自动重试。 */
|
|
7
|
+
export interface IdempotencyOptions { idempotencyKey?: string }
|
|
8
|
+
|
|
9
|
+
/** App Settings 条件写入要求使用刚读取到的 revision。 */
|
|
10
|
+
export interface VersionCondition extends IdempotencyOptions { expectedRevision: number }
|
|
11
|
+
|
|
12
|
+
/** 当前 App 的单个持久化配置。 */
|
|
13
|
+
export interface AppSetting { key: string; value: JsonValue; revision: number }
|
|
14
|
+
|
|
15
|
+
/** 只列出当前 App 的有效配置,不含清空元数据。 */
|
|
16
|
+
export function getSettings(): Promise<Readonly<{ items: AppSetting[] }>>;
|
|
17
|
+
|
|
18
|
+
/** 不存在时返回 null/revision=0,清空后的 null 保留正 revision。 */
|
|
19
|
+
export function getSetting(key: string): Promise<Readonly<AppSetting>>;
|
|
20
|
+
|
|
21
|
+
/** null 清空;容量、大小和深度限制由平台原子校验。 */
|
|
22
|
+
export function setSetting(key: string, value: JsonValue, options: VersionCondition): Promise<Readonly<AppSetting>>;
|
|
23
|
+
|
|
24
|
+
/** 当前 App Installation 隔离的字符串 KV。 */
|
|
25
|
+
export interface AppKv {
|
|
26
|
+
/** 读取字符串;不存在或已过期时返回 null。 */
|
|
27
|
+
get(key: string): Promise<string | null>;
|
|
28
|
+
/** 原子覆盖字符串,可选正整数 TTL 秒数。 */
|
|
29
|
+
set(key: string, value: string, options?: { ttlInSeconds?: number }): Promise<void>;
|
|
30
|
+
/** 幂等删除字符串。 */
|
|
31
|
+
delete(key: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 当前执行可用的 Installation 级 KV。 */
|
|
35
|
+
export const kv: AppKv;
|
|
36
|
+
|
|
37
|
+
/** 当前 Installation 已激活的稳定 Webhook Handler。 */
|
|
38
|
+
export interface WebhookHandler {
|
|
39
|
+
/** 平台生成的稳定 Handler ID。 */
|
|
40
|
+
id: string;
|
|
41
|
+
/** 与源码文件对应的稳定名称。 */
|
|
42
|
+
fileName: string;
|
|
43
|
+
/** 可注册到外部系统或 Workspace Webhook 的平台回调地址。 */
|
|
44
|
+
url: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 幂等激活当前 Manifest 已声明的稳定 Webhook Handler。 */
|
|
48
|
+
export function createWebhookHandler(input: {
|
|
49
|
+
/** `src/app/webhooks/<fileName>.webhook.ts` 中的 fileName。 */
|
|
50
|
+
fileName: string;
|
|
51
|
+
}): Promise<Readonly<WebhookHandler>>;
|
|
52
|
+
|
|
53
|
+
/** 列出当前 Installation 已激活的 Webhook Handlers。 */
|
|
54
|
+
export function listWebhookHandlers(): Promise<readonly Readonly<WebhookHandler>[]>;
|
|
55
|
+
|
|
56
|
+
/** 幂等停用当前 Installation 自己的 Webhook Handler。 */
|
|
57
|
+
export function deleteWebhookHandler(id: string): Promise<void>;
|
|
58
|
+
|
|
59
|
+
/** Workspace Webhook 当前公开的 Record 事件目录。 */
|
|
60
|
+
export type WorkspaceWebhookRecordEventType =
|
|
61
|
+
| "record.created"
|
|
62
|
+
| "record.updated"
|
|
63
|
+
| "record.deleted"
|
|
64
|
+
| "record.archived"
|
|
65
|
+
| "record.restored";
|
|
66
|
+
|
|
67
|
+
/** Workspace Webhook 当前公开的 Task 事件目录。 */
|
|
68
|
+
export type WorkspaceWebhookTaskEventType =
|
|
69
|
+
| "task.created"
|
|
70
|
+
| "task.updated"
|
|
71
|
+
| "task.deleted";
|
|
72
|
+
|
|
73
|
+
/** Workspace Webhook 当前公开的 Note 事件目录。 */
|
|
74
|
+
export type WorkspaceWebhookNoteEventType =
|
|
75
|
+
| "note.created"
|
|
76
|
+
| "note.updated"
|
|
77
|
+
| "note.deleted";
|
|
78
|
+
|
|
79
|
+
/** Workspace Webhook 当前公开的 Activity 事件目录。 */
|
|
80
|
+
export type WorkspaceWebhookActivityEventType =
|
|
81
|
+
| "activity.created"
|
|
82
|
+
| "activity.updated"
|
|
83
|
+
| "activity.deleted";
|
|
84
|
+
|
|
85
|
+
/** Workspace Webhook 当前公开的 Worklist Entry 事件目录。 */
|
|
86
|
+
export type WorkspaceWebhookListEntryEventType =
|
|
87
|
+
| "list-entry.created"
|
|
88
|
+
| "list-entry.updated"
|
|
89
|
+
| "list-entry.deleted";
|
|
90
|
+
|
|
91
|
+
/** Webhook 通知中的可信操作者引用。 */
|
|
92
|
+
export interface WorkspaceWebhookActorReference {
|
|
93
|
+
/** 操作者类型。 */
|
|
94
|
+
type: "human" | "agent" | "automation" | "integration" | "system";
|
|
95
|
+
/** 稳定主体 ID;无具体主体的 system 操作可以为空。 */
|
|
96
|
+
id: string | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Webhook 通知中的 Record 与可选字段 Slug。 */
|
|
100
|
+
export interface WorkspaceWebhookRecordIdentity {
|
|
101
|
+
/** 当前 Workspace ID。 */
|
|
102
|
+
workspaceId: string;
|
|
103
|
+
/** 当前公开对象 Slug。 */
|
|
104
|
+
objectSlug: string;
|
|
105
|
+
/** Record 实例 ID。 */
|
|
106
|
+
recordId: string;
|
|
107
|
+
/** 仅 `record.updated` 存在的当前公开字段 Slug。 */
|
|
108
|
+
fieldSlug?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 最小 Record Webhook Event;业务值需要通过 Record API 查询。 */
|
|
112
|
+
export interface WorkspaceWebhookRecordEvent {
|
|
113
|
+
/** Record 事件类型。 */
|
|
114
|
+
eventType: WorkspaceWebhookRecordEventType;
|
|
115
|
+
/** 事件涉及的资源 identity。 */
|
|
116
|
+
id: WorkspaceWebhookRecordIdentity;
|
|
117
|
+
/** source fact 无可信 actor 时为空。 */
|
|
118
|
+
actor: WorkspaceWebhookActorReference | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Webhook 通知中的 Task identity。 */
|
|
122
|
+
export interface WorkspaceWebhookTaskIdentity {
|
|
123
|
+
/** 当前 Workspace ID。 */
|
|
124
|
+
workspaceId: string;
|
|
125
|
+
/** Task 实例 ID。 */
|
|
126
|
+
taskId: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 最小 Task Webhook Event;业务值需要通过 Task API 查询。 */
|
|
130
|
+
export interface WorkspaceWebhookTaskEvent {
|
|
131
|
+
/** Task 事件类型。 */
|
|
132
|
+
eventType: WorkspaceWebhookTaskEventType;
|
|
133
|
+
/** 事件涉及的 Task identity。 */
|
|
134
|
+
id: WorkspaceWebhookTaskIdentity;
|
|
135
|
+
/** source fact 无可信 actor 时为空。 */
|
|
136
|
+
actor: WorkspaceWebhookActorReference | null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Webhook 通知中的 Note identity。 */
|
|
140
|
+
export interface WorkspaceWebhookNoteIdentity {
|
|
141
|
+
/** 当前 Workspace ID。 */
|
|
142
|
+
workspaceId: string;
|
|
143
|
+
/** Note 实例 ID。 */
|
|
144
|
+
noteId: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 最小 Note Webhook Event;正文和其他业务值需要通过 Note API 查询。 */
|
|
148
|
+
export interface WorkspaceWebhookNoteEvent {
|
|
149
|
+
/** Note 事件类型。 */
|
|
150
|
+
eventType: WorkspaceWebhookNoteEventType;
|
|
151
|
+
/** 事件涉及的 Note identity。 */
|
|
152
|
+
id: WorkspaceWebhookNoteIdentity;
|
|
153
|
+
/** source fact 无可信 actor 时为空。 */
|
|
154
|
+
actor: WorkspaceWebhookActorReference | null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Webhook 通知中的 Activity identity。 */
|
|
158
|
+
export interface WorkspaceWebhookActivityIdentity {
|
|
159
|
+
/** 当前 Workspace ID。 */
|
|
160
|
+
workspaceId: string;
|
|
161
|
+
/** Activity 实例 ID。 */
|
|
162
|
+
activityId: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 最小 Activity Webhook Event;业务值需要通过 Activity API 查询。 */
|
|
166
|
+
export interface WorkspaceWebhookActivityEvent {
|
|
167
|
+
/** Activity 事件类型。 */
|
|
168
|
+
eventType: WorkspaceWebhookActivityEventType;
|
|
169
|
+
/** 事件涉及的 Activity identity。 */
|
|
170
|
+
id: WorkspaceWebhookActivityIdentity;
|
|
171
|
+
/** source fact 无可信 actor 时为空。 */
|
|
172
|
+
actor: WorkspaceWebhookActorReference | null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Webhook 通知中的 Worklist Entry identity。 */
|
|
176
|
+
export interface WorkspaceWebhookListEntryIdentity {
|
|
177
|
+
workspaceId: string;
|
|
178
|
+
listId: string;
|
|
179
|
+
entryId: string;
|
|
180
|
+
objectSlug: string;
|
|
181
|
+
recordId: string;
|
|
182
|
+
/** 仅 `list-entry.updated` 存在。 */
|
|
183
|
+
fieldSlug?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 最小 Worklist Entry Webhook Event;业务值需要通过 Entry API 回查。 */
|
|
187
|
+
export interface WorkspaceWebhookListEntryEvent {
|
|
188
|
+
eventType: WorkspaceWebhookListEntryEventType;
|
|
189
|
+
id: WorkspaceWebhookListEntryIdentity;
|
|
190
|
+
actor: WorkspaceWebhookActorReference | null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Workspace Webhook 当前公开的封闭事件联合。 */
|
|
194
|
+
export type WorkspaceWebhookEvent =
|
|
195
|
+
| WorkspaceWebhookRecordEvent
|
|
196
|
+
| WorkspaceWebhookTaskEvent
|
|
197
|
+
| WorkspaceWebhookNoteEvent
|
|
198
|
+
| WorkspaceWebhookActivityEvent
|
|
199
|
+
| WorkspaceWebhookListEntryEvent;
|
|
200
|
+
|
|
201
|
+
/** Workspace Webhook HTTP 请求体。 */
|
|
202
|
+
export interface WorkspaceWebhookBody {
|
|
203
|
+
/** 当前 Webhook ID。 */
|
|
204
|
+
webhookId: string;
|
|
205
|
+
/** 本次投递携带的事件;接收方不得假设永久只有一条。 */
|
|
206
|
+
events: WorkspaceWebhookEvent[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** 当前服务端执行可使用的 Workspace 外部系统连接。 */
|
|
210
|
+
export interface WorkspaceConnection {
|
|
211
|
+
/** 当前 Workspace Connection 的稳定 ID。 */
|
|
212
|
+
id: string;
|
|
213
|
+
/** 当前 Connect/Reconnect generation 的非敏感稳定表示。 */
|
|
214
|
+
generation: string;
|
|
215
|
+
/** 当前凭据类型。 */
|
|
216
|
+
type: "secret" | "oauth2";
|
|
217
|
+
/** Secret 或当前 OAuth access token,仅存在于本次服务端执行内存。 */
|
|
218
|
+
value: string;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** 同步读取当前执行的连接快照;未连接或暂不可用时抛出安全错误,不在 getter 内刷新。 */
|
|
222
|
+
export function getWorkspaceConnection(): Readonly<WorkspaceConnection>;
|
|
223
|
+
|
|
224
|
+
/** Server Function 的一期函数合同。 */
|
|
225
|
+
export type ServerFunction<TArgs extends JsonValue[] = JsonValue[], TResult extends JsonValue = JsonValue> =
|
|
226
|
+
(...args: TArgs) => Promise<TResult>;
|
|
227
|
+
|
|
228
|
+
/** App 安装通知处理器取得的可信参数。 */
|
|
229
|
+
export interface AppInstalledParams {
|
|
230
|
+
/** 当前 Installation 的稳定 ID。 */
|
|
231
|
+
installationId: string;
|
|
232
|
+
/** 当前 Workspace 的稳定 ID。 */
|
|
233
|
+
workspaceId: string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** App 卸载通知处理器取得的可信参数。 */
|
|
237
|
+
export interface AppUninstalledParams {
|
|
238
|
+
/** 当前 Installation 的稳定 ID。 */
|
|
239
|
+
installationId: string;
|
|
240
|
+
/** 当前 Workspace 的稳定 ID。 */
|
|
241
|
+
workspaceId: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Connection 建立前 interceptor 取得的可信参数。 */
|
|
245
|
+
export interface ConnectionAddedParams {
|
|
246
|
+
/** 本次候选 Connection,只在当前 interceptor 执行内可用。 */
|
|
247
|
+
connection: Readonly<WorkspaceConnection>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Connection 移除前 interceptor 取得的可信参数。 */
|
|
251
|
+
export interface ConnectionRemovedParams {
|
|
252
|
+
/** 本次待移除 Connection,只在当前 interceptor 执行内可用。 */
|
|
253
|
+
connection: Readonly<WorkspaceConnection>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
/** 在前端调用的服务端函数中,写入前按当前可信调用者重新校验管理员权限;不接受前端自报角色。 */
|
|
258
|
+
export function requireWorkspaceAdmin(): Promise<void>;
|
|
259
|
+
|
|
260
|
+
/** 平台注入的单次 Webhook 执行上下文,不能由入站请求覆盖。 */
|
|
261
|
+
export interface WebhookContext {
|
|
262
|
+
/** 本次入站请求的可信执行 ID;不是业务幂等保证。 */
|
|
263
|
+
readonly invocationId: string;
|
|
264
|
+
/** 每次实际执行的独立记录 ID。 */
|
|
265
|
+
readonly attemptId: string;
|
|
266
|
+
/** 从 1 开始的业务尝试序号。 */
|
|
267
|
+
readonly attemptNumber: number;
|
|
268
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const SERVER_FUNCTION_MARKER = Symbol.for("zhike.server-function.v1");
|
|
2
|
+
const LIFECYCLE_EVENT_MARKER = Symbol.for("zhike.lifecycle-event.v1");
|
|
3
|
+
const WEBHOOK_HANDLER_MARKER = Symbol.for("zhike.webhook-handler.v1");
|
|
4
|
+
const WORKSPACE_CONNECTION_EVENTS = new Set([
|
|
5
|
+
"app-installed",
|
|
6
|
+
"app-uninstalled",
|
|
7
|
+
"connection-added",
|
|
8
|
+
"connection-removed",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export const ZHIKE_API_TOKEN = globalThis.ZHIKE_API_TOKEN;
|
|
12
|
+
const ZHIKE_OPEN_API_BASE_URL = "https://api-clm.77hub.com/v1";
|
|
13
|
+
export const kv = globalThis.ZHIKE_APP_KV;
|
|
14
|
+
|
|
15
|
+
function requireSettingKey(key) {
|
|
16
|
+
if (typeof key !== "string" || !key || key === "." || key === "..") {
|
|
17
|
+
throw new TypeError("setting_key_invalid");
|
|
18
|
+
}
|
|
19
|
+
return encodeURIComponent(key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function requestAppSetting(path, { method = "GET", value, expectedRevision, idempotencyKey } = {}) {
|
|
23
|
+
const token = globalThis.ZHIKE_API_TOKEN ?? ZHIKE_API_TOKEN;
|
|
24
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
25
|
+
const error = new Error("open_api_token_unavailable");
|
|
26
|
+
error.code = "open_api_token_unavailable";
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
30
|
+
if (idempotencyKey !== undefined) {
|
|
31
|
+
if (typeof idempotencyKey !== "string" || !/^[!-~]{1,200}$/.test(idempotencyKey)) {
|
|
32
|
+
throw new TypeError("idempotency_key_invalid");
|
|
33
|
+
}
|
|
34
|
+
headers["idempotency-key"] = idempotencyKey;
|
|
35
|
+
}
|
|
36
|
+
if (value !== undefined) {
|
|
37
|
+
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
|
|
38
|
+
throw new TypeError("expected_revision_invalid");
|
|
39
|
+
}
|
|
40
|
+
headers["if-match"] = `"${expectedRevision}"`;
|
|
41
|
+
headers["content-type"] = "application/json";
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(`${ZHIKE_OPEN_API_BASE_URL}${path}`, {
|
|
44
|
+
method,
|
|
45
|
+
headers,
|
|
46
|
+
...(value === undefined ? {} : { body: JSON.stringify({ value }) }),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const body = await response.json().catch(() => ({}));
|
|
50
|
+
const code = typeof body.code === "string" && /^[a-z_]{1,100}$/.test(body.code)
|
|
51
|
+
? body.code
|
|
52
|
+
: "app_setting_request_failed";
|
|
53
|
+
const error = new Error(code);
|
|
54
|
+
error.code = code;
|
|
55
|
+
error.status = response.status;
|
|
56
|
+
error.requestId = typeof body.requestId === "string" ? body.requestId : null;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
return Object.freeze(await response.json());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 读取当前 App 的全部有效配置。 */
|
|
63
|
+
export const getSettings = () => requestAppSetting("/app-settings");
|
|
64
|
+
/** 读取当前 App 的单个配置及 revision。 */
|
|
65
|
+
export const getSetting = (key) => requestAppSetting(`/app-settings/${requireSettingKey(key)}`);
|
|
66
|
+
/** 条件写入当前 App 的单个配置;null 清空但保留 revision。 */
|
|
67
|
+
export const setSetting = (key, value, options = {}) => {
|
|
68
|
+
if (value === undefined) throw new TypeError("setting_value_required");
|
|
69
|
+
return requestAppSetting(`/app-settings/${requireSettingKey(key)}`, {
|
|
70
|
+
method: "PUT",
|
|
71
|
+
value,
|
|
72
|
+
expectedRevision: options.expectedRevision,
|
|
73
|
+
idempotencyKey: options.idempotencyKey,
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export async function createWebhookHandler({ fileName } = {}) {
|
|
78
|
+
if (typeof fileName !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(fileName)) {
|
|
79
|
+
const error = new Error("webhook_handler_file_name_invalid");
|
|
80
|
+
error.code = "webhook_handler_file_name_invalid";
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
const capability = globalThis.ZHIKE_WEBHOOK_HANDLER_CAPABILITY;
|
|
84
|
+
const handler = capability?.create
|
|
85
|
+
? await capability.create(fileName)
|
|
86
|
+
: globalThis.ZHIKE_WEBHOOK_HANDLERS?.[fileName];
|
|
87
|
+
if (
|
|
88
|
+
!handler ||
|
|
89
|
+
typeof handler !== "object" ||
|
|
90
|
+
handler.fileName !== fileName ||
|
|
91
|
+
typeof handler.id !== "string" ||
|
|
92
|
+
typeof handler.url !== "string"
|
|
93
|
+
) {
|
|
94
|
+
const error = new Error("webhook_handler_not_declared");
|
|
95
|
+
error.code = "webhook_handler_not_declared";
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return Object.freeze({ id: handler.id, fileName, url: handler.url });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function listWebhookHandlers() {
|
|
102
|
+
const capability = globalThis.ZHIKE_WEBHOOK_HANDLER_CAPABILITY;
|
|
103
|
+
const handlers = capability?.list
|
|
104
|
+
? await capability.list()
|
|
105
|
+
: Object.values(globalThis.ZHIKE_WEBHOOK_HANDLERS ?? {});
|
|
106
|
+
if (!Array.isArray(handlers)) {
|
|
107
|
+
const error = new Error("webhook_handler_runtime_unavailable");
|
|
108
|
+
error.code = "webhook_handler_runtime_unavailable";
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze(handlers.map((handler) => Object.freeze({ ...handler })));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function deleteWebhookHandler(id) {
|
|
115
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
116
|
+
const error = new Error("webhook_handler_id_invalid");
|
|
117
|
+
error.code = "webhook_handler_id_invalid";
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
const capability = globalThis.ZHIKE_WEBHOOK_HANDLER_CAPABILITY;
|
|
121
|
+
if (!capability?.delete) {
|
|
122
|
+
const error = new Error("webhook_handler_runtime_unavailable");
|
|
123
|
+
error.code = "webhook_handler_runtime_unavailable";
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
await capability.delete(id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 同步读取本次执行快照;旧 dispatcher 的 exact lifecycle global 仅作兼容。
|
|
130
|
+
export function getWorkspaceConnection() {
|
|
131
|
+
const current = globalThis.ZHIKE_CURRENT_WORKSPACE_CONNECTION;
|
|
132
|
+
const connection = globalThis.ZHIKE_WORKSPACE_CONNECTION ??
|
|
133
|
+
(current?.status === "ready" ? current.connection : null);
|
|
134
|
+
if (
|
|
135
|
+
!connection ||
|
|
136
|
+
typeof connection !== "object" ||
|
|
137
|
+
typeof connection.id !== "string" ||
|
|
138
|
+
typeof connection.generation !== "string" ||
|
|
139
|
+
!["oauth2", "secret"].includes(connection.type) ||
|
|
140
|
+
typeof connection.value !== "string"
|
|
141
|
+
) {
|
|
142
|
+
const code = ["connection_not_configured", "connection_not_connected", "connection_credential_unavailable", "connection_temporarily_unavailable"].includes(current?.code)
|
|
143
|
+
? current.code : "connection_unavailable";
|
|
144
|
+
const error = new Error(code);
|
|
145
|
+
error.code = code;
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
return Object.freeze({
|
|
149
|
+
id: connection.id,
|
|
150
|
+
generation: connection.generation,
|
|
151
|
+
type: connection.type,
|
|
152
|
+
value: connection.value,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 仅兼容已构建的旧 Bundle;新源码入口由 .server.ts 的默认 async 函数确定。
|
|
157
|
+
export function defineServerFunction(handler) {
|
|
158
|
+
if (typeof handler !== "function") {
|
|
159
|
+
throw new TypeError("Server Function handler 必须是函数");
|
|
160
|
+
}
|
|
161
|
+
Object.defineProperty(handler, SERVER_FUNCTION_MARKER, { value: true });
|
|
162
|
+
return handler;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 仅保留已发布旧 Bundle 的运行时兼容;新源码必须使用约定式默认 async function。
|
|
166
|
+
export function defineLifecycleEvent(definition) {
|
|
167
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
168
|
+
throw new TypeError("Lifecycle Event definition 必须是对象");
|
|
169
|
+
}
|
|
170
|
+
if (!WORKSPACE_CONNECTION_EVENTS.has(definition.event)) {
|
|
171
|
+
throw new TypeError("Lifecycle Event 类型不受支持");
|
|
172
|
+
}
|
|
173
|
+
if (typeof definition.handler !== "function") {
|
|
174
|
+
throw new TypeError("Lifecycle Event handler 必须是函数");
|
|
175
|
+
}
|
|
176
|
+
return Object.freeze({ ...definition, [LIFECYCLE_EVENT_MARKER]: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function defineWebhookHandler(definition) {
|
|
180
|
+
if (!definition || typeof definition !== "object" || Array.isArray(definition)) {
|
|
181
|
+
throw new TypeError("Webhook Handler definition 必须是对象");
|
|
182
|
+
}
|
|
183
|
+
if (!Array.isArray(definition.methods) || definition.methods.length === 0) {
|
|
184
|
+
throw new TypeError("Webhook Handler methods 不能为空");
|
|
185
|
+
}
|
|
186
|
+
if (typeof definition.handler !== "function") {
|
|
187
|
+
throw new TypeError("Webhook Handler handler 必须是函数");
|
|
188
|
+
}
|
|
189
|
+
return Object.freeze({ ...definition, [WEBHOOK_HANDLER_MARKER]: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
export async function requireWorkspaceAdmin() {
|
|
194
|
+
const check = globalThis.ZHIKE_CALLER_CAPABILITY?.requireWorkspaceAdmin;
|
|
195
|
+
if (typeof check !== "function") {
|
|
196
|
+
const error = new Error("runtime_function_caller_unavailable");
|
|
197
|
+
error.code = "runtime_function_caller_unavailable";
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
await check();
|
|
201
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function canonicalJsonBytes(value) {
|
|
2
|
+
return Buffer.from(`${JSON.stringify(sortJson(value))}\n`, "utf8");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function sortJson(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map(sortJson);
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === "object") {
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
Object.keys(value)
|
|
12
|
+
.sort()
|
|
13
|
+
.map((key) => [key, sortJson(value[key])]),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { TOOLING_VERSION } from "../config.js";
|
|
6
|
+
import { ZhikeCliError } from "../errors.js";
|
|
7
|
+
import { readProjectBinding } from "../project-binding.js";
|
|
8
|
+
|
|
9
|
+
export { discoverAppSources } from "./source-discovery.js";
|
|
10
|
+
export {
|
|
11
|
+
APP_BUNDLE_CONTENT_TYPE,
|
|
12
|
+
APP_BUNDLE_PACKAGE_FORMAT,
|
|
13
|
+
APP_MANIFEST_MEDIA_TYPE,
|
|
14
|
+
JAVASCRIPT_MEDIA_TYPE,
|
|
15
|
+
buildAppBundle,
|
|
16
|
+
} from "./real-build.js";
|
|
17
|
+
export {
|
|
18
|
+
APP_BUNDLE_ALLOWED_MEMBERS,
|
|
19
|
+
APP_BUNDLE_MEMBER_MAX_BYTES,
|
|
20
|
+
APP_BUNDLE_TAR_MAX_BYTES,
|
|
21
|
+
createTransportTar,
|
|
22
|
+
parseTransportTar,
|
|
23
|
+
} from "./transport-tar.js";
|
|
24
|
+
|
|
25
|
+
export const MOCK_PACKAGE_FORMAT = "zhike.mock-build.v1";
|
|
26
|
+
export const MOCK_ARTIFACT_KIND = "mock";
|
|
27
|
+
export const MOCK_ARTIFACT_MAX_BYTES = 1024 * 1024;
|
|
28
|
+
|
|
29
|
+
export async function buildMockArtifact({
|
|
30
|
+
projectDirectory = process.cwd(),
|
|
31
|
+
expectedEnvironment = null,
|
|
32
|
+
writeToDist = false,
|
|
33
|
+
} = {}) {
|
|
34
|
+
await readProjectBinding(projectDirectory, expectedEnvironment);
|
|
35
|
+
const payload = {
|
|
36
|
+
format: MOCK_PACKAGE_FORMAT,
|
|
37
|
+
kind: MOCK_ARTIFACT_KIND,
|
|
38
|
+
payload: {
|
|
39
|
+
message: "qiqizhike developer app mock build",
|
|
40
|
+
},
|
|
41
|
+
toolingVersion: TOOLING_VERSION,
|
|
42
|
+
};
|
|
43
|
+
const bytes = Buffer.from(`${JSON.stringify(payload)}\n`, "utf8");
|
|
44
|
+
if (bytes.byteLength > MOCK_ARTIFACT_MAX_BYTES) {
|
|
45
|
+
throw new ZhikeCliError(
|
|
46
|
+
"zhike_mock_artifact_too_large",
|
|
47
|
+
"Mock Artifact 超过 1 MiB 上限",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const result = Object.freeze({
|
|
51
|
+
bytes,
|
|
52
|
+
size: bytes.byteLength,
|
|
53
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
54
|
+
packageFormat: MOCK_PACKAGE_FORMAT,
|
|
55
|
+
kind: MOCK_ARTIFACT_KIND,
|
|
56
|
+
toolingVersion: TOOLING_VERSION,
|
|
57
|
+
});
|
|
58
|
+
if (writeToDist) {
|
|
59
|
+
const distDirectory = resolve(projectDirectory, "dist");
|
|
60
|
+
await mkdir(distDirectory, { recursive: true });
|
|
61
|
+
await writeFile(resolve(distDirectory, "mock-build.json"), bytes, {
|
|
62
|
+
flag: "w",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function validateMockArtifact(bytes) {
|
|
69
|
+
if (!Buffer.isBuffer(bytes) || bytes.byteLength > MOCK_ARTIFACT_MAX_BYTES) {
|
|
70
|
+
throw new ZhikeCliError(
|
|
71
|
+
"zhike_mock_artifact_invalid",
|
|
72
|
+
"Mock Artifact 无效或超过 1 MiB 上限",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
let value;
|
|
76
|
+
try {
|
|
77
|
+
value = JSON.parse(bytes.toString("utf8"));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new ZhikeCliError(
|
|
80
|
+
"zhike_mock_artifact_invalid",
|
|
81
|
+
"Mock Artifact 不是有效 JSON",
|
|
82
|
+
{ cause: error },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const keys = Object.keys(value).sort();
|
|
86
|
+
if (
|
|
87
|
+
keys.join(",") !== "format,kind,payload,toolingVersion" ||
|
|
88
|
+
value.format !== MOCK_PACKAGE_FORMAT ||
|
|
89
|
+
value.kind !== MOCK_ARTIFACT_KIND ||
|
|
90
|
+
value.toolingVersion !== TOOLING_VERSION ||
|
|
91
|
+
!value.payload ||
|
|
92
|
+
typeof value.payload !== "object" ||
|
|
93
|
+
Array.isArray(value.payload) ||
|
|
94
|
+
Object.keys(value.payload).join(",") !== "message" ||
|
|
95
|
+
value.payload.message !== "qiqizhike developer app mock build"
|
|
96
|
+
) {
|
|
97
|
+
throw new ZhikeCliError(
|
|
98
|
+
"zhike_mock_artifact_invalid",
|
|
99
|
+
"Mock Artifact 不符合 zhike.mock-build.v1 严格合同",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|