@gaoding/cli 0.0.0 → 1.0.0-alpha.25
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/LICENSE +21 -0
- package/README.md +40 -3
- package/contracts/operations/agent.send/input.schema.json +353 -0
- package/contracts/operations/agent.send/output.schema.json +271 -0
- package/contracts/operations/auth.status/input.schema.json +7 -0
- package/contracts/operations/auth.status/output.schema.json +132 -0
- package/contracts/operations/dam.copy/input.schema.json +19 -0
- package/contracts/operations/dam.copy/output.schema.json +19 -0
- package/contracts/operations/dam.delete/input.schema.json +23 -0
- package/contracts/operations/dam.delete/output.schema.json +6 -0
- package/contracts/operations/dam.download/input.schema.json +14 -0
- package/contracts/operations/dam.download/output.schema.json +25 -0
- package/contracts/operations/dam.folder.list/input.schema.json +15 -0
- package/contracts/operations/dam.folder.list/output.schema.json +37 -0
- package/contracts/operations/dam.get/input.schema.json +16 -0
- package/contracts/operations/dam.get/output.schema.json +46 -0
- package/contracts/operations/dam.list/input.schema.json +44 -0
- package/contracts/operations/dam.list/output.schema.json +71 -0
- package/contracts/operations/dam.move/input.schema.json +19 -0
- package/contracts/operations/dam.move/output.schema.json +18 -0
- package/contracts/operations/dam.recycle.list/input.schema.json +13 -0
- package/contracts/operations/dam.recycle.list/output.schema.json +34 -0
- package/contracts/operations/dam.recycle.restore/input.schema.json +14 -0
- package/contracts/operations/dam.recycle.restore/output.schema.json +6 -0
- package/contracts/operations/dam.rename/input.schema.json +18 -0
- package/contracts/operations/dam.rename/output.schema.json +18 -0
- package/contracts/operations/dam.repository.list/input.schema.json +8 -0
- package/contracts/operations/dam.repository.list/output.schema.json +52 -0
- package/contracts/operations/dam.search/input.schema.json +46 -0
- package/contracts/operations/dam.search/output.schema.json +71 -0
- package/contracts/operations/dam.tag.list/input.schema.json +12 -0
- package/contracts/operations/dam.tag.list/output.schema.json +26 -0
- package/contracts/operations/dam.upload/input.schema.json +31 -0
- package/contracts/operations/dam.upload/output.schema.json +46 -0
- package/contracts/operations/editor.apply/input.schema.json +134 -0
- package/contracts/operations/editor.apply/output.schema.json +15 -0
- package/contracts/operations/editor.connect/input.schema.json +15 -0
- package/contracts/operations/editor.connect/output.schema.json +17 -0
- package/contracts/operations/editor.disconnect/input.schema.json +8 -0
- package/contracts/operations/editor.disconnect/output.schema.json +14 -0
- package/contracts/operations/editor.save/input.schema.json +8 -0
- package/contracts/operations/editor.save/output.schema.json +23 -0
- package/contracts/operations/editor.screenshot/input.schema.json +8 -0
- package/contracts/operations/editor.screenshot/output.schema.json +16 -0
- package/contracts/operations/editor.snapshot/input.schema.json +8 -0
- package/contracts/operations/editor.snapshot/output.schema.json +113 -0
- package/contracts/operations/model.get/input.schema.json +18 -0
- package/contracts/operations/model.get/output.schema.json +191 -0
- package/contracts/operations/model.list/input.schema.json +15 -0
- package/contracts/operations/model.list/output.schema.json +111 -0
- package/contracts/operations/org.current/input.schema.json +7 -0
- package/contracts/operations/org.current/output.schema.json +49 -0
- package/contracts/operations/org.list/input.schema.json +7 -0
- package/contracts/operations/org.list/output.schema.json +71 -0
- package/contracts/operations/tool.call/input.schema.json +35 -0
- package/contracts/operations/tool.call/output.schema.json +129 -0
- package/contracts/operations/tool.list/input.schema.json +7 -0
- package/contracts/operations/tool.list/output.schema.json +45 -0
- package/dist/bin/gd-cli.js +60 -0
- package/dist/bin/postinstall.js +42 -0
- package/dist/src/bootstrap/create-cli.js +112 -0
- package/dist/src/bootstrap/create-runtime.js +290 -0
- package/dist/src/bootstrap/validators.js +157 -0
- package/dist/src/cli/action-binding.js +109 -0
- package/dist/src/cli/agent-commands.js +49 -0
- package/dist/src/cli/auth-commands.js +55 -0
- package/dist/src/cli/dam-commands.js +469 -0
- package/dist/src/cli/editor-commands.js +73 -0
- package/dist/src/cli/errors.js +157 -0
- package/dist/src/cli/model-commands.js +61 -0
- package/dist/src/cli/org-commands.js +58 -0
- package/dist/src/cli/presenter.js +105 -0
- package/dist/src/cli/prompt.js +30 -0
- package/dist/src/cli/tool-commands.js +77 -0
- package/dist/src/cli/update-command.js +14 -0
- package/dist/src/contracts/schema.js +4 -0
- package/dist/src/features/agent/creative-agent-adapter.js +36 -0
- package/dist/src/features/agent/creative-protocol.js +432 -0
- package/dist/src/features/agent/creative-stream.js +127 -0
- package/dist/src/features/agent/use-cases.js +150 -0
- package/dist/src/features/auth/access-policy.js +101 -0
- package/dist/src/features/auth/credential-store.js +62 -0
- package/dist/src/features/auth/sso-service.js +194 -0
- package/dist/src/features/auth/state.js +129 -0
- package/dist/src/features/auth/use-cases.js +136 -0
- package/dist/src/features/dam/asset-projection.js +278 -0
- package/dist/src/features/dam/dam-api-adapter.js +562 -0
- package/dist/src/features/dam/file-downloader.js +242 -0
- package/dist/src/features/dam/object-storage.js +193 -0
- package/dist/src/features/dam/operation-executor.js +72 -0
- package/dist/src/features/dam/registered-uploader.js +247 -0
- package/dist/src/features/dam/repository-catalog.js +127 -0
- package/dist/src/features/dam/storage-upload.js +141 -0
- package/dist/src/features/dam/transient-uploader.js +17 -0
- package/dist/src/features/dam/use-cases.js +470 -0
- package/dist/src/features/editor/bridge-client.js +109 -0
- package/dist/src/features/editor/bridge-process.js +222 -0
- package/dist/src/features/editor/bridge-server.js +337 -0
- package/dist/src/features/editor/protocol.js +1 -0
- package/dist/src/features/editor/public-url.js +36 -0
- package/dist/src/features/editor/session-state.js +78 -0
- package/dist/src/features/editor/session.js +207 -0
- package/dist/src/features/editor/use-cases.js +88 -0
- package/dist/src/features/org/org-service.js +76 -0
- package/dist/src/features/org/use-cases.js +156 -0
- package/dist/src/features/skill/bundled-skills.js +55 -0
- package/dist/src/features/skill/installer.js +271 -0
- package/dist/src/features/tool/catalog.js +83 -0
- package/dist/src/features/tool/dynamic-schema.js +128 -0
- package/dist/src/features/tool/mns-catalog-adapter.js +265 -0
- package/dist/src/features/tool/tool-api-adapter.js +352 -0
- package/dist/src/features/tool/use-cases.js +177 -0
- package/dist/src/features/update/latest-version.js +37 -0
- package/dist/src/features/update/update-notification.js +262 -0
- package/dist/src/features/update/update-service.js +183 -0
- package/dist/src/platform/json-input.js +67 -0
- package/dist/src/platform/local-json-file.js +59 -0
- package/dist/src/platform/open-browser.js +8 -0
- package/dist/src/platform/redact.js +80 -0
- package/dist/src/platform/remote-error-evidence.js +80 -0
- package/dist/src/platform/remote-protocol.js +10 -0
- package/dist/src/platform/safe-upload-file.js +146 -0
- package/dist/src/platform/signature.js +20 -0
- package/dist/src/platform/signed-http-transport.js +143 -0
- package/dist/src/platform/url-safety.js +100 -0
- package/dist/src/telemetry/invocation.js +156 -0
- package/dist/src/telemetry/sls-sink.js +31 -0
- package/package.json +54 -8
- package/skills/gd-cli/SKILL.md +15 -0
- package/skills/gd-cli/references/auth-org.md +9 -0
- package/skills/gd-cli/references/creation.md +115 -0
- package/skills/gd-cli/references/dam.md +52 -0
- package/skills/gd-cli/references/editor.md +254 -0
- package/skills/gd-cli/references/errors.md +13 -0
- package/skills/gd-cli/references/update.md +13 -0
- package/index.js +0 -3
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { DamOperationUnconfirmedError } from "./operation-executor.js";
|
|
4
|
+
export class DamInputError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("DAM 输入不可用。");
|
|
7
|
+
this.name = "DamInputError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class DamRepositoryNotFoundError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("当前组织没有可用的 DAM 资源库。");
|
|
13
|
+
this.name = "DamRepositoryNotFoundError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export { DamOperationUnconfirmedError } from "./operation-executor.js";
|
|
17
|
+
function invalid() {
|
|
18
|
+
throw new DamInputError();
|
|
19
|
+
}
|
|
20
|
+
function nonblank(value) {
|
|
21
|
+
return value !== undefined && value.trim() !== "";
|
|
22
|
+
}
|
|
23
|
+
function assertPageSize(value) {
|
|
24
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 1 || value > 100))
|
|
25
|
+
invalid();
|
|
26
|
+
}
|
|
27
|
+
function assertPage(value) {
|
|
28
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 1))
|
|
29
|
+
invalid();
|
|
30
|
+
}
|
|
31
|
+
function assertOptionalNonblank(value) {
|
|
32
|
+
if (value !== undefined && !nonblank(value))
|
|
33
|
+
invalid();
|
|
34
|
+
}
|
|
35
|
+
function assertAssetId(value) {
|
|
36
|
+
if (value.trim() === "" || /^https?:\/\//iu.test(value))
|
|
37
|
+
invalid();
|
|
38
|
+
}
|
|
39
|
+
function assertRepositorySelection(input) {
|
|
40
|
+
if (input.repositoryId !== undefined && !nonblank(input.repositoryId))
|
|
41
|
+
invalid();
|
|
42
|
+
if (input.repositoryId !== undefined && input.allRepositories === true)
|
|
43
|
+
invalid();
|
|
44
|
+
}
|
|
45
|
+
function searchRequest(input, keyword, repositoryIds) {
|
|
46
|
+
return {
|
|
47
|
+
keyword,
|
|
48
|
+
repositoryIds,
|
|
49
|
+
includeChildFolder: input.includeChildFolder ?? true,
|
|
50
|
+
pageSize: input.pageSize ?? 20,
|
|
51
|
+
...(input.folderId === undefined ? {} : { folderId: input.folderId }),
|
|
52
|
+
...(input.type === undefined ? {} : { type: input.type }),
|
|
53
|
+
...(input.assetFormats === undefined ? {} : { assetFormats: [...input.assetFormats] }),
|
|
54
|
+
...(input.tagIds === undefined ? {} : { tagIds: [...input.tagIds] }),
|
|
55
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor })
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function createDamUseCases(dependencies) {
|
|
59
|
+
const validate = dependencies.validate;
|
|
60
|
+
async function entryTitle(input) {
|
|
61
|
+
if (input.kind === "asset") {
|
|
62
|
+
return (await dependencies.api.getAsset({
|
|
63
|
+
assetId: input.entryId,
|
|
64
|
+
access: input.access,
|
|
65
|
+
signal: input.signal
|
|
66
|
+
})).title;
|
|
67
|
+
}
|
|
68
|
+
return (await dependencies.api.getFolder({
|
|
69
|
+
folderId: input.entryId,
|
|
70
|
+
repositoryId: input.repositoryId,
|
|
71
|
+
access: input.access,
|
|
72
|
+
signal: input.signal
|
|
73
|
+
})).title;
|
|
74
|
+
}
|
|
75
|
+
async function entryLocation(input) {
|
|
76
|
+
if (input.kind === "asset") {
|
|
77
|
+
const detail = await dependencies.api.getAsset({
|
|
78
|
+
assetId: input.entryId,
|
|
79
|
+
access: input.access,
|
|
80
|
+
signal: input.signal
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
...(detail.repository_id === undefined ? {} : { repositoryId: detail.repository_id }),
|
|
84
|
+
...(detail.folder_id === undefined ? {} : { folderId: detail.folder_id })
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const detail = await dependencies.api.getFolder({
|
|
88
|
+
folderId: input.entryId,
|
|
89
|
+
repositoryId: input.repositoryId,
|
|
90
|
+
access: input.access,
|
|
91
|
+
signal: input.signal
|
|
92
|
+
});
|
|
93
|
+
return { repositoryId: detail.repository_id, folderId: detail.folder_id };
|
|
94
|
+
}
|
|
95
|
+
function assertEntryOperationInput(input) {
|
|
96
|
+
assertAssetId(input.entry_id);
|
|
97
|
+
if (input.kind !== "asset" && input.kind !== "folder")
|
|
98
|
+
invalid();
|
|
99
|
+
if (!nonblank(input.targetFolderId))
|
|
100
|
+
invalid();
|
|
101
|
+
assertOptionalNonblank(input.repositoryId);
|
|
102
|
+
assertOptionalNonblank(input.targetRepositoryId);
|
|
103
|
+
if (input.kind === "folder" && input.entry_id === input.targetFolderId)
|
|
104
|
+
invalid();
|
|
105
|
+
}
|
|
106
|
+
async function entryOperation(input) {
|
|
107
|
+
assertEntryOperationInput(input.request);
|
|
108
|
+
const [sourceRepository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
109
|
+
...(input.request.repositoryId === undefined
|
|
110
|
+
? {}
|
|
111
|
+
: { repositoryId: input.request.repositoryId })
|
|
112
|
+
}, input.access, input.signal)));
|
|
113
|
+
if (sourceRepository === undefined)
|
|
114
|
+
throw new DamRepositoryNotFoundError();
|
|
115
|
+
const targetRepositoryId = input.request.targetRepositoryId ?? sourceRepository.id;
|
|
116
|
+
const locationRequest = {
|
|
117
|
+
entryId: input.request.entry_id,
|
|
118
|
+
kind: input.request.kind,
|
|
119
|
+
repositoryId: sourceRepository.id,
|
|
120
|
+
access: input.access,
|
|
121
|
+
signal: input.signal
|
|
122
|
+
};
|
|
123
|
+
const current = await dependencies.telemetry.stage("resolve", () => (entryLocation(locationRequest)));
|
|
124
|
+
if (input.operation === "move"
|
|
125
|
+
&& current.repositoryId === targetRepositoryId
|
|
126
|
+
&& current.folderId === input.request.targetFolderId)
|
|
127
|
+
invalid();
|
|
128
|
+
const execution = await dependencies.telemetry.stage("wait", () => (dependencies.operations.execute({
|
|
129
|
+
operation: input.operation,
|
|
130
|
+
submit: () => dependencies.telemetry.stage("submit", () => (dependencies.api.submitEntryOperation({
|
|
131
|
+
entryId: input.request.entry_id,
|
|
132
|
+
kind: input.request.kind,
|
|
133
|
+
operation: input.operation,
|
|
134
|
+
sourceRepositoryId: sourceRepository.id,
|
|
135
|
+
targetRepositoryId,
|
|
136
|
+
targetFolderId: input.request.targetFolderId,
|
|
137
|
+
access: input.access,
|
|
138
|
+
signal: input.signal
|
|
139
|
+
}))),
|
|
140
|
+
getTask: (taskId, signal) => dependencies.api.getOperationTask({
|
|
141
|
+
taskId,
|
|
142
|
+
access: input.access,
|
|
143
|
+
signal
|
|
144
|
+
}),
|
|
145
|
+
...(input.operation === "copy"
|
|
146
|
+
? {
|
|
147
|
+
inspectResult: (taskId, signal) => (dependencies.api.getOperationChildren({
|
|
148
|
+
taskId,
|
|
149
|
+
access: input.access,
|
|
150
|
+
signal
|
|
151
|
+
}))
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
154
|
+
signal: input.signal
|
|
155
|
+
})));
|
|
156
|
+
return {
|
|
157
|
+
sourceRepositoryId: sourceRepository.id,
|
|
158
|
+
targetRepositoryId,
|
|
159
|
+
targetFolderId: input.request.targetFolderId,
|
|
160
|
+
...(execution.copied_entry_id === undefined
|
|
161
|
+
? {}
|
|
162
|
+
: { copiedEntryId: execution.copied_entry_id })
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function resolveRepositories(input, access, signal) {
|
|
166
|
+
assertRepositorySelection(input);
|
|
167
|
+
if (input.repositoryId !== undefined)
|
|
168
|
+
return [{ id: input.repositoryId }];
|
|
169
|
+
signal.throwIfAborted();
|
|
170
|
+
const available = await dependencies.repositories.list({ access, signal });
|
|
171
|
+
signal.throwIfAborted();
|
|
172
|
+
if (available.length === 0)
|
|
173
|
+
throw new DamRepositoryNotFoundError();
|
|
174
|
+
const selected = input.allRepositories === true
|
|
175
|
+
? available
|
|
176
|
+
: [available.find((repository) => repository.is_default) ?? available[0]];
|
|
177
|
+
return selected.map((repository) => ({
|
|
178
|
+
id: repository.repository_id,
|
|
179
|
+
...(repository.name === undefined ? {} : { name: repository.name })
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
async repositoryList({ access, signal }) {
|
|
184
|
+
signal.throwIfAborted();
|
|
185
|
+
const repositories = await dependencies.repositories.list({ access, signal });
|
|
186
|
+
signal.throwIfAborted();
|
|
187
|
+
if (repositories.length === 0)
|
|
188
|
+
throw new DamRepositoryNotFoundError();
|
|
189
|
+
const result = { repositories };
|
|
190
|
+
validate.repositoryList(result);
|
|
191
|
+
return result;
|
|
192
|
+
},
|
|
193
|
+
async list({ input, access, signal }) {
|
|
194
|
+
signal.throwIfAborted();
|
|
195
|
+
assertPageSize(input.pageSize);
|
|
196
|
+
const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
|
|
197
|
+
return dependencies.telemetry.stage("request", async () => {
|
|
198
|
+
const result = await dependencies.api.search({
|
|
199
|
+
...searchRequest(input, "", repositories.map((repository) => repository.id)),
|
|
200
|
+
access,
|
|
201
|
+
signal
|
|
202
|
+
});
|
|
203
|
+
signal.throwIfAborted();
|
|
204
|
+
validate.list(result);
|
|
205
|
+
return result;
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
async search({ input, access, signal }) {
|
|
209
|
+
signal.throwIfAborted();
|
|
210
|
+
if (!nonblank(input.query))
|
|
211
|
+
invalid();
|
|
212
|
+
assertPageSize(input.pageSize);
|
|
213
|
+
const repositories = await dependencies.telemetry.stage("repository", () => (resolveRepositories(input, access, signal)));
|
|
214
|
+
return dependencies.telemetry.stage("request", async () => {
|
|
215
|
+
const result = await dependencies.api.search({
|
|
216
|
+
...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
|
|
217
|
+
access,
|
|
218
|
+
signal
|
|
219
|
+
});
|
|
220
|
+
signal.throwIfAborted();
|
|
221
|
+
validate.search(result);
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
async folderList({ input, access, signal }) {
|
|
226
|
+
signal.throwIfAborted();
|
|
227
|
+
assertOptionalNonblank(input.parentId);
|
|
228
|
+
assertOptionalNonblank(input.query);
|
|
229
|
+
assertPage(input.page);
|
|
230
|
+
assertPageSize(input.pageSize);
|
|
231
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
232
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
233
|
+
}, access, signal)));
|
|
234
|
+
if (repository === undefined)
|
|
235
|
+
throw new DamRepositoryNotFoundError();
|
|
236
|
+
return dependencies.telemetry.stage("request", async () => {
|
|
237
|
+
const result = await dependencies.api.listFolders({
|
|
238
|
+
repositoryId: repository.id,
|
|
239
|
+
parentId: input.parentId ?? "0",
|
|
240
|
+
page: input.page ?? 1,
|
|
241
|
+
pageSize: input.pageSize ?? 20,
|
|
242
|
+
...(input.query === undefined ? {} : { query: input.query }),
|
|
243
|
+
access,
|
|
244
|
+
signal
|
|
245
|
+
});
|
|
246
|
+
signal.throwIfAborted();
|
|
247
|
+
validate.folderList(result);
|
|
248
|
+
return result;
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
async tagList({ input, access, signal }) {
|
|
252
|
+
signal.throwIfAborted();
|
|
253
|
+
assertOptionalNonblank(input.query);
|
|
254
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
255
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
256
|
+
}, access, signal)));
|
|
257
|
+
if (repository === undefined)
|
|
258
|
+
throw new DamRepositoryNotFoundError();
|
|
259
|
+
return dependencies.telemetry.stage("request", async () => {
|
|
260
|
+
const result = await dependencies.api.listTags({
|
|
261
|
+
repositoryId: repository.id,
|
|
262
|
+
...(input.query === undefined ? {} : { query: input.query }),
|
|
263
|
+
access,
|
|
264
|
+
signal
|
|
265
|
+
});
|
|
266
|
+
signal.throwIfAborted();
|
|
267
|
+
validate.tagList(result);
|
|
268
|
+
return result;
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
async download({ input, access, signal }) {
|
|
272
|
+
signal.throwIfAborted();
|
|
273
|
+
assertAssetId(input.asset_id);
|
|
274
|
+
assertOptionalNonblank(input.outputDir);
|
|
275
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
276
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
277
|
+
}, access, signal)));
|
|
278
|
+
if (repository === undefined)
|
|
279
|
+
throw new DamRepositoryNotFoundError();
|
|
280
|
+
const detail = await dependencies.telemetry.stage("resolve", () => (dependencies.api.getAsset({ assetId: input.asset_id, access, signal })));
|
|
281
|
+
signal.throwIfAborted();
|
|
282
|
+
const urls = await dependencies.telemetry.stage("download", () => (dependencies.api.getDownloadUrls({
|
|
283
|
+
assetId: input.asset_id,
|
|
284
|
+
repositoryId: repository.id,
|
|
285
|
+
access,
|
|
286
|
+
signal
|
|
287
|
+
})));
|
|
288
|
+
signal.throwIfAborted();
|
|
289
|
+
const files = await dependencies.telemetry.stage("persist", () => (dependencies.downloader.download({
|
|
290
|
+
urls,
|
|
291
|
+
...(detail.title === undefined ? {} : { fallbackTitle: detail.title }),
|
|
292
|
+
...(detail.format === undefined ? {} : { fallbackFormat: detail.format }),
|
|
293
|
+
outputDirectory: input.outputDir ?? ".",
|
|
294
|
+
signal
|
|
295
|
+
})));
|
|
296
|
+
signal.throwIfAborted();
|
|
297
|
+
const result = { asset_id: input.asset_id, files };
|
|
298
|
+
validate.download(result);
|
|
299
|
+
return result;
|
|
300
|
+
},
|
|
301
|
+
async rename({ input, access, signal }) {
|
|
302
|
+
signal.throwIfAborted();
|
|
303
|
+
assertAssetId(input.entry_id);
|
|
304
|
+
if (input.kind !== "asset" && input.kind !== "folder")
|
|
305
|
+
invalid();
|
|
306
|
+
if (!nonblank(input.title) || input.title.length > 100)
|
|
307
|
+
invalid();
|
|
308
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
309
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
310
|
+
}, access, signal)));
|
|
311
|
+
if (repository === undefined)
|
|
312
|
+
throw new DamRepositoryNotFoundError();
|
|
313
|
+
const request = {
|
|
314
|
+
entryId: input.entry_id,
|
|
315
|
+
kind: input.kind,
|
|
316
|
+
repositoryId: repository.id,
|
|
317
|
+
access,
|
|
318
|
+
signal
|
|
319
|
+
};
|
|
320
|
+
const currentTitle = await dependencies.telemetry.stage("rename", async () => {
|
|
321
|
+
const title = await entryTitle(request);
|
|
322
|
+
signal.throwIfAborted();
|
|
323
|
+
if (title?.trim() !== input.title.trim()) {
|
|
324
|
+
await dependencies.api.renameEntry({
|
|
325
|
+
...request,
|
|
326
|
+
repositoryId: repository.id,
|
|
327
|
+
title: input.title
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return title;
|
|
331
|
+
});
|
|
332
|
+
if (currentTitle?.trim() === input.title.trim()) {
|
|
333
|
+
const result = {
|
|
334
|
+
entry_id: input.entry_id,
|
|
335
|
+
kind: input.kind,
|
|
336
|
+
repository_id: repository.id,
|
|
337
|
+
title: currentTitle
|
|
338
|
+
};
|
|
339
|
+
validate.rename(result);
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
await dependencies.telemetry.stage("verify", async () => {
|
|
343
|
+
for (let attempt = 0; attempt <= 30; attempt += 1) {
|
|
344
|
+
signal.throwIfAborted();
|
|
345
|
+
if (await entryTitle(request) === input.title)
|
|
346
|
+
return;
|
|
347
|
+
if (attempt < 30)
|
|
348
|
+
await dependencies.sleep(2_000, signal);
|
|
349
|
+
}
|
|
350
|
+
throw new DamOperationUnconfirmedError("rename");
|
|
351
|
+
});
|
|
352
|
+
const result = {
|
|
353
|
+
entry_id: input.entry_id,
|
|
354
|
+
kind: input.kind,
|
|
355
|
+
repository_id: repository.id,
|
|
356
|
+
title: input.title
|
|
357
|
+
};
|
|
358
|
+
validate.rename(result);
|
|
359
|
+
return result;
|
|
360
|
+
},
|
|
361
|
+
async move({ input, access, signal }) {
|
|
362
|
+
signal.throwIfAborted();
|
|
363
|
+
const operation = await entryOperation({ operation: "move", request: input, access, signal });
|
|
364
|
+
await dependencies.telemetry.stage("verify", async () => {
|
|
365
|
+
for (let attempt = 0; attempt <= 30; attempt += 1) {
|
|
366
|
+
signal.throwIfAborted();
|
|
367
|
+
const location = await entryLocation({
|
|
368
|
+
entryId: input.entry_id,
|
|
369
|
+
kind: input.kind,
|
|
370
|
+
repositoryId: operation.targetRepositoryId,
|
|
371
|
+
access,
|
|
372
|
+
signal
|
|
373
|
+
});
|
|
374
|
+
if (location.repositoryId === operation.targetRepositoryId
|
|
375
|
+
&& location.folderId === operation.targetFolderId)
|
|
376
|
+
return;
|
|
377
|
+
if (attempt < 30)
|
|
378
|
+
await dependencies.sleep(2_000, signal);
|
|
379
|
+
}
|
|
380
|
+
throw new DamOperationUnconfirmedError("move");
|
|
381
|
+
});
|
|
382
|
+
const result = {
|
|
383
|
+
entry_id: input.entry_id,
|
|
384
|
+
kind: input.kind,
|
|
385
|
+
repository_id: operation.targetRepositoryId,
|
|
386
|
+
folder_id: operation.targetFolderId
|
|
387
|
+
};
|
|
388
|
+
await dependencies.telemetry.stage("output", () => { validate.move(result); });
|
|
389
|
+
return result;
|
|
390
|
+
},
|
|
391
|
+
async copy({ input, access, signal }) {
|
|
392
|
+
signal.throwIfAborted();
|
|
393
|
+
const operation = await entryOperation({ operation: "copy", request: input, access, signal });
|
|
394
|
+
const result = {
|
|
395
|
+
source_entry_id: input.entry_id,
|
|
396
|
+
kind: input.kind,
|
|
397
|
+
repository_id: operation.targetRepositoryId,
|
|
398
|
+
folder_id: operation.targetFolderId,
|
|
399
|
+
...(operation.copiedEntryId === undefined
|
|
400
|
+
? {}
|
|
401
|
+
: { copied_entry_id: operation.copiedEntryId })
|
|
402
|
+
};
|
|
403
|
+
await dependencies.telemetry.stage("output", () => { validate.copy(result); });
|
|
404
|
+
return result;
|
|
405
|
+
},
|
|
406
|
+
async get({ input, access, signal }) {
|
|
407
|
+
signal.throwIfAborted();
|
|
408
|
+
assertAssetId(input.asset_id);
|
|
409
|
+
return dependencies.telemetry.stage("request", async () => {
|
|
410
|
+
const result = await dependencies.api.getAsset({
|
|
411
|
+
assetId: input.asset_id,
|
|
412
|
+
access,
|
|
413
|
+
signal
|
|
414
|
+
});
|
|
415
|
+
signal.throwIfAborted();
|
|
416
|
+
validate.get(result);
|
|
417
|
+
return result;
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
async upload({ input, access, signal }) {
|
|
421
|
+
signal.throwIfAborted();
|
|
422
|
+
if (!nonblank(input.file))
|
|
423
|
+
invalid();
|
|
424
|
+
const file = await dependencies.telemetry.stage("inspect", () => (dependencies.storage.inspect({
|
|
425
|
+
url: pathToFileURL(resolve(process.cwd(), input.file)).href,
|
|
426
|
+
...(input.allowSensitivePath === undefined
|
|
427
|
+
? {}
|
|
428
|
+
: { allowSensitivePath: input.allowSensitivePath })
|
|
429
|
+
}, signal)));
|
|
430
|
+
signal.throwIfAborted();
|
|
431
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
432
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
433
|
+
}, access, signal)));
|
|
434
|
+
if (repository === undefined)
|
|
435
|
+
throw new DamRepositoryNotFoundError();
|
|
436
|
+
const result = await dependencies.uploader.upload({
|
|
437
|
+
input,
|
|
438
|
+
file,
|
|
439
|
+
repository,
|
|
440
|
+
access,
|
|
441
|
+
signal
|
|
442
|
+
});
|
|
443
|
+
signal.throwIfAborted();
|
|
444
|
+
validate.upload(result);
|
|
445
|
+
return result;
|
|
446
|
+
},
|
|
447
|
+
async delete({ input, access, signal }) {
|
|
448
|
+
signal.throwIfAborted();
|
|
449
|
+
assertAssetId(input.asset_id);
|
|
450
|
+
const [repository] = await dependencies.telemetry.stage("repository", () => (resolveRepositories({
|
|
451
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
452
|
+
}, access, signal)));
|
|
453
|
+
if (repository === undefined)
|
|
454
|
+
throw new DamRepositoryNotFoundError();
|
|
455
|
+
const request = {
|
|
456
|
+
repositoryId: repository.id,
|
|
457
|
+
assetId: input.asset_id,
|
|
458
|
+
access,
|
|
459
|
+
signal
|
|
460
|
+
};
|
|
461
|
+
await dependencies.telemetry.stage("recycle", () => dependencies.api.recycle(request));
|
|
462
|
+
signal.throwIfAborted();
|
|
463
|
+
if (input.permanent === true) {
|
|
464
|
+
await dependencies.telemetry.stage("delete", () => dependencies.api.deleteRecycled(request));
|
|
465
|
+
}
|
|
466
|
+
signal.throwIfAborted();
|
|
467
|
+
validate.delete(null);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export class EditorBridgeError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "EditorBridgeError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class EditorBridgeUnavailableError extends Error {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("Editor Bridge is unavailable.");
|
|
12
|
+
this.name = "EditorBridgeUnavailableError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function createEditorBridgeClient(options) {
|
|
16
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
17
|
+
const baseUrl = `http://127.0.0.1:${options.state.port}`;
|
|
18
|
+
return {
|
|
19
|
+
async health(signal) {
|
|
20
|
+
const value = await request("/health", "GET", undefined, signal);
|
|
21
|
+
if (!isRecord(value) ||
|
|
22
|
+
!hasExactKeys(value, ["status", "pageConnected", "protocolVersion"]) ||
|
|
23
|
+
value.status !== "ok" ||
|
|
24
|
+
typeof value.pageConnected !== "boolean" ||
|
|
25
|
+
typeof value.protocolVersion !== "number") {
|
|
26
|
+
throw invalidResponse();
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
status: "ok",
|
|
30
|
+
pageConnected: value.pageConnected,
|
|
31
|
+
protocolVersion: value.protocolVersion
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
async rpc(method, payload, signal) {
|
|
35
|
+
const value = await request("/rpc", "POST", { method, ...(payload === undefined ? {} : { payload }) }, signal);
|
|
36
|
+
if (!isRecord(value) ||
|
|
37
|
+
(!hasExactKeys(value, []) && !hasExactKeys(value, ["result"]))) {
|
|
38
|
+
throw invalidResponse();
|
|
39
|
+
}
|
|
40
|
+
return value.result;
|
|
41
|
+
},
|
|
42
|
+
async disconnect(signal) {
|
|
43
|
+
const value = await request("/disconnect", "POST", {}, signal);
|
|
44
|
+
if (!isRecord(value) ||
|
|
45
|
+
!hasExactKeys(value, ["status"]) ||
|
|
46
|
+
value.status !== "disconnected") {
|
|
47
|
+
throw invalidResponse();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
async function request(pathname, method, body, signal) {
|
|
52
|
+
signal.throwIfAborted();
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await fetch(`${baseUrl}${pathname}`, {
|
|
56
|
+
method,
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${options.state.token}`,
|
|
59
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" })
|
|
60
|
+
},
|
|
61
|
+
redirect: "error",
|
|
62
|
+
signal,
|
|
63
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) })
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
signal.throwIfAborted();
|
|
68
|
+
throw new EditorBridgeUnavailableError();
|
|
69
|
+
}
|
|
70
|
+
let value;
|
|
71
|
+
try {
|
|
72
|
+
value = await response.json();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw invalidResponse();
|
|
76
|
+
}
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const error = readStructuredError(value);
|
|
79
|
+
if (!error)
|
|
80
|
+
throw invalidResponse();
|
|
81
|
+
throw new EditorBridgeError(error.code, error.message);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function readStructuredError(value) {
|
|
87
|
+
if (!isRecord(value) || !isRecord(value.error))
|
|
88
|
+
return null;
|
|
89
|
+
const { code, message } = value.error;
|
|
90
|
+
if (typeof code !== "string" ||
|
|
91
|
+
!/^[A-Z][A-Z0-9_]*$/.test(code) ||
|
|
92
|
+
typeof message !== "string" ||
|
|
93
|
+
message.trim().length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return { code, message };
|
|
97
|
+
}
|
|
98
|
+
function invalidResponse() {
|
|
99
|
+
return new EditorBridgeError("EDITOR_BRIDGE_INVALID_RESPONSE", "Editor Bridge 返回了无效响应。");
|
|
100
|
+
}
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
103
|
+
}
|
|
104
|
+
function hasExactKeys(value, keys) {
|
|
105
|
+
const actual = Object.keys(value).sort();
|
|
106
|
+
const expected = [...keys].sort();
|
|
107
|
+
return actual.length === expected.length &&
|
|
108
|
+
actual.every((key, index) => key === expected[index]);
|
|
109
|
+
}
|