gaoding-cli 1.0.0-alpha.10
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 +38 -0
- package/contracts/operations/agent.send/input.schema.json +354 -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.delete/input.schema.json +23 -0
- package/contracts/operations/dam.delete/output.schema.json +6 -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.search/input.schema.json +46 -0
- package/contracts/operations/dam.search/output.schema.json +71 -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 +190 -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 +38 -0
- package/dist/bin/postinstall.js +42 -0
- package/dist/src/bootstrap/create-cli.js +55 -0
- package/dist/src/bootstrap/create-runtime.js +222 -0
- package/dist/src/bootstrap/validators.js +118 -0
- package/dist/src/cli/action-binding.js +63 -0
- package/dist/src/cli/agent-commands.js +39 -0
- package/dist/src/cli/auth-commands.js +47 -0
- package/dist/src/cli/dam-commands.js +207 -0
- package/dist/src/cli/editor-commands.js +73 -0
- package/dist/src/cli/errors.js +90 -0
- package/dist/src/cli/model-commands.js +47 -0
- package/dist/src/cli/org-commands.js +52 -0
- package/dist/src/cli/presenter.js +80 -0
- package/dist/src/cli/prompt.js +30 -0
- package/dist/src/cli/tool-commands.js +65 -0
- package/dist/src/cli/update-command.js +12 -0
- package/dist/src/contracts/schema.js +4 -0
- package/dist/src/features/agent/creative-agent-adapter.js +31 -0
- package/dist/src/features/agent/creative-protocol.js +324 -0
- package/dist/src/features/agent/creative-stream.js +127 -0
- package/dist/src/features/agent/use-cases.js +113 -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 +179 -0
- package/dist/src/features/auth/state.js +129 -0
- package/dist/src/features/auth/use-cases.js +106 -0
- package/dist/src/features/dam/asset-projection.js +270 -0
- package/dist/src/features/dam/dam-api-adapter.js +206 -0
- package/dist/src/features/dam/object-storage.js +191 -0
- package/dist/src/features/dam/registered-uploader.js +224 -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 +151 -0
- package/dist/src/features/editor/bridge-client.js +96 -0
- package/dist/src/features/editor/bridge-process.js +222 -0
- package/dist/src/features/editor/bridge-server.js +311 -0
- package/dist/src/features/editor/protocol.js +1 -0
- package/dist/src/features/editor/session-state.js +78 -0
- package/dist/src/features/editor/session.js +193 -0
- package/dist/src/features/editor/use-cases.js +61 -0
- package/dist/src/features/org/org-service.js +76 -0
- package/dist/src/features/org/use-cases.js +140 -0
- package/dist/src/features/skill/bundled-skills.js +55 -0
- package/dist/src/features/skill/installer.js +265 -0
- package/dist/src/features/tool/catalog.js +79 -0
- package/dist/src/features/tool/dynamic-schema.js +96 -0
- package/dist/src/features/tool/mns-catalog-adapter.js +262 -0
- package/dist/src/features/tool/tool-api-adapter.js +200 -0
- package/dist/src/features/tool/use-cases.js +126 -0
- package/dist/src/features/update/update-service.js +194 -0
- package/dist/src/platform/json-input.js +64 -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/safe-upload-file.js +146 -0
- package/dist/src/platform/signature.js +19 -0
- package/dist/src/platform/signed-http-transport.js +109 -0
- package/dist/src/platform/url-safety.js +100 -0
- package/package.json +56 -0
- 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 +38 -0
- package/skills/gd-cli/references/dam.md +20 -0
- package/skills/gd-cli/references/editor.md +12 -0
- package/skills/gd-cli/references/errors.md +10 -0
- package/skills/gd-cli/references/update.md +11 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { isSensitiveKey } from "../../platform/redact.js";
|
|
2
|
+
import { parseSafeRemoteAssetUrl } from "../../platform/url-safety.js";
|
|
3
|
+
const publicAssetTypes = new Set([
|
|
4
|
+
"image",
|
|
5
|
+
"video",
|
|
6
|
+
"audio",
|
|
7
|
+
"font",
|
|
8
|
+
"template",
|
|
9
|
+
"file"
|
|
10
|
+
]);
|
|
11
|
+
const recognizableAssetKeys = new Set([
|
|
12
|
+
"asset_id",
|
|
13
|
+
"id",
|
|
14
|
+
"content_id",
|
|
15
|
+
"title",
|
|
16
|
+
"name",
|
|
17
|
+
"description",
|
|
18
|
+
"format",
|
|
19
|
+
"type",
|
|
20
|
+
"content_format",
|
|
21
|
+
"storage_format",
|
|
22
|
+
"origin_info",
|
|
23
|
+
"originInfo",
|
|
24
|
+
"origin_url",
|
|
25
|
+
"url",
|
|
26
|
+
"preview",
|
|
27
|
+
"preview_info",
|
|
28
|
+
"previewInfo",
|
|
29
|
+
"cover",
|
|
30
|
+
"cover_url",
|
|
31
|
+
"analysis_result",
|
|
32
|
+
"analysis_status"
|
|
33
|
+
]);
|
|
34
|
+
function record(value) {
|
|
35
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
36
|
+
? value
|
|
37
|
+
: undefined;
|
|
38
|
+
}
|
|
39
|
+
function nonblank(value) {
|
|
40
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
41
|
+
}
|
|
42
|
+
function finiteNumber(value) {
|
|
43
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
function findAssetRecord(value) {
|
|
46
|
+
const queue = [value];
|
|
47
|
+
const seen = new Set();
|
|
48
|
+
while (queue.length > 0) {
|
|
49
|
+
const candidate = queue.shift();
|
|
50
|
+
if (Array.isArray(candidate)) {
|
|
51
|
+
queue.push(...candidate);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const item = record(candidate);
|
|
55
|
+
if (item === undefined || seen.has(item))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(item);
|
|
58
|
+
if (Object.keys(item).some((key) => recognizableAssetKeys.has(key)))
|
|
59
|
+
return item;
|
|
60
|
+
for (const key of ["data", "asset", "asset_info", "item", "result"]) {
|
|
61
|
+
if (item[key] !== undefined)
|
|
62
|
+
queue.push(item[key]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
function safePublicUrl(value) {
|
|
68
|
+
const raw = nonblank(value);
|
|
69
|
+
if (raw === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
try {
|
|
72
|
+
const url = parseSafeRemoteAssetUrl(raw);
|
|
73
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
74
|
+
if (isSensitiveKey(key))
|
|
75
|
+
url.searchParams.delete(key);
|
|
76
|
+
}
|
|
77
|
+
return url.href;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function timestamp(value) {
|
|
84
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
85
|
+
const milliseconds = Math.abs(value) < 1_000_000_000_000 ? value * 1000 : value;
|
|
86
|
+
const date = new Date(milliseconds);
|
|
87
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
88
|
+
}
|
|
89
|
+
const text = nonblank(value);
|
|
90
|
+
if (text === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
const date = new Date(text);
|
|
93
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
94
|
+
}
|
|
95
|
+
function tags(value) {
|
|
96
|
+
if (!Array.isArray(value))
|
|
97
|
+
return undefined;
|
|
98
|
+
const result = value
|
|
99
|
+
.map((item) => typeof item === "string" ? nonblank(item) : nonblank(record(item)?.name))
|
|
100
|
+
.filter((item) => item !== undefined);
|
|
101
|
+
return result.length === 0 ? undefined : result;
|
|
102
|
+
}
|
|
103
|
+
function tagIds(tagIdsValue, tagsValue) {
|
|
104
|
+
const candidates = Array.isArray(tagIdsValue)
|
|
105
|
+
? tagIdsValue
|
|
106
|
+
: Array.isArray(tagsValue)
|
|
107
|
+
? tagsValue.map((item) => record(item)?.id)
|
|
108
|
+
: [];
|
|
109
|
+
const result = candidates.filter((item) => (typeof item === "string" && item.trim() !== "")
|
|
110
|
+
|| (typeof item === "number" && Number.isFinite(item)));
|
|
111
|
+
return result.length === 0 ? undefined : result;
|
|
112
|
+
}
|
|
113
|
+
function assetType(item, format, mediaType) {
|
|
114
|
+
const explicit = nonblank(item.type)?.toLowerCase();
|
|
115
|
+
if (explicit !== undefined && publicAssetTypes.has(explicit))
|
|
116
|
+
return explicit;
|
|
117
|
+
const contentFormat = (nonblank(item.content_format) ?? nonblank(item.storage_format))?.toLowerCase();
|
|
118
|
+
if (contentFormat === "gdpic" || contentFormat === "gdimage")
|
|
119
|
+
return "image";
|
|
120
|
+
if (contentFormat === "gdvideo")
|
|
121
|
+
return "video";
|
|
122
|
+
if (contentFormat === "gdaudio")
|
|
123
|
+
return "audio";
|
|
124
|
+
if (contentFormat === "gdfont")
|
|
125
|
+
return "font";
|
|
126
|
+
if (contentFormat === "gdtemplate")
|
|
127
|
+
return "template";
|
|
128
|
+
if (contentFormat === "gdfile" || contentFormat === "gdslide")
|
|
129
|
+
return "file";
|
|
130
|
+
const mediaCategory = mediaType?.split("/", 1)[0]?.toLowerCase();
|
|
131
|
+
if (mediaCategory !== undefined && publicAssetTypes.has(mediaCategory))
|
|
132
|
+
return mediaCategory;
|
|
133
|
+
const normalizedFormat = format?.toLowerCase();
|
|
134
|
+
if (normalizedFormat !== undefined
|
|
135
|
+
&& ["png", "jpg", "jpeg", "gif", "webp", "svg", "avif", "heic"].includes(normalizedFormat)) {
|
|
136
|
+
return "image";
|
|
137
|
+
}
|
|
138
|
+
if (normalizedFormat !== undefined
|
|
139
|
+
&& ["mp4", "mov", "webm", "mkv"].includes(normalizedFormat))
|
|
140
|
+
return "video";
|
|
141
|
+
if (normalizedFormat !== undefined
|
|
142
|
+
&& ["mp3", "wav", "m4a", "aac", "ogg", "flac"].includes(normalizedFormat)) {
|
|
143
|
+
return "audio";
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
function compact(value) {
|
|
148
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
149
|
+
}
|
|
150
|
+
function normalizeAsset(value, fallback) {
|
|
151
|
+
const item = findAssetRecord(value);
|
|
152
|
+
if (item === undefined)
|
|
153
|
+
return undefined;
|
|
154
|
+
const fallbackAsset = typeof fallback === "object" ? fallback : undefined;
|
|
155
|
+
const origin = record(item.origin_info) ?? record(item.originInfo);
|
|
156
|
+
const design = record(item.design_info) ?? record(item.designInfo);
|
|
157
|
+
const preview = record(item.preview)
|
|
158
|
+
?? record(item.preview_info)
|
|
159
|
+
?? record(item.previewInfo)
|
|
160
|
+
?? record(item.cover);
|
|
161
|
+
const repository = record(item.repository_info) ?? record(item.repositoryInfo);
|
|
162
|
+
const folder = record(item.folder_info) ?? record(item.folderInfo);
|
|
163
|
+
const format = nonblank(origin?.format) ?? nonblank(item.format);
|
|
164
|
+
const mediaType = nonblank(origin?.mime_type)
|
|
165
|
+
?? nonblank(item.mime_type)
|
|
166
|
+
?? nonblank(item.mimeType);
|
|
167
|
+
const assetId = nonblank(item.asset_id)
|
|
168
|
+
?? nonblank(item.id)
|
|
169
|
+
?? nonblank(item.content_id)
|
|
170
|
+
?? (typeof fallback === "string" ? fallback : fallbackAsset?.asset_id);
|
|
171
|
+
if (assetId === undefined)
|
|
172
|
+
return undefined;
|
|
173
|
+
return compact({
|
|
174
|
+
...fallbackAsset,
|
|
175
|
+
asset_id: assetId,
|
|
176
|
+
title: nonblank(item.title) ?? nonblank(item.name) ?? fallbackAsset?.title,
|
|
177
|
+
description: nonblank(item.description) ?? fallbackAsset?.description,
|
|
178
|
+
format: format ?? fallbackAsset?.format,
|
|
179
|
+
type: assetType(item, format, mediaType) ?? fallbackAsset?.type,
|
|
180
|
+
size: finiteNumber(origin?.size) ?? finiteNumber(item.size) ?? fallbackAsset?.size,
|
|
181
|
+
url: safePublicUrl(origin?.url ?? item.origin_url ?? item.url) ?? fallbackAsset?.url,
|
|
182
|
+
cover_url: safePublicUrl(preview?.url ?? item.cover_url ?? item.preview_url) ?? fallbackAsset?.cover_url,
|
|
183
|
+
width: finiteNumber(design?.width)
|
|
184
|
+
?? finiteNumber(preview?.width)
|
|
185
|
+
?? finiteNumber(item.width)
|
|
186
|
+
?? fallbackAsset?.width,
|
|
187
|
+
height: finiteNumber(design?.height)
|
|
188
|
+
?? finiteNumber(preview?.height)
|
|
189
|
+
?? finiteNumber(item.height)
|
|
190
|
+
?? fallbackAsset?.height,
|
|
191
|
+
duration: finiteNumber(design?.duration)
|
|
192
|
+
?? finiteNumber(preview?.duration)
|
|
193
|
+
?? finiteNumber(item.duration)
|
|
194
|
+
?? fallbackAsset?.duration,
|
|
195
|
+
repository_id: nonblank(item.repository_id)
|
|
196
|
+
?? nonblank(repository?.id)
|
|
197
|
+
?? nonblank(repository?.repository_id)
|
|
198
|
+
?? fallbackAsset?.repository_id,
|
|
199
|
+
repository_name: nonblank(item.repository_name)
|
|
200
|
+
?? nonblank(repository?.name)
|
|
201
|
+
?? nonblank(repository?.title)
|
|
202
|
+
?? fallbackAsset?.repository_name,
|
|
203
|
+
folder_id: nonblank(item.folder_id)
|
|
204
|
+
?? nonblank(folder?.id)
|
|
205
|
+
?? fallbackAsset?.folder_id,
|
|
206
|
+
folder_name: nonblank(item.folder_name)
|
|
207
|
+
?? nonblank(folder?.name)
|
|
208
|
+
?? nonblank(folder?.title)
|
|
209
|
+
?? fallbackAsset?.folder_name,
|
|
210
|
+
tags: tags(item.tags),
|
|
211
|
+
tag_ids: tagIds(item.tag_ids, item.tags) ?? fallbackAsset?.tag_ids,
|
|
212
|
+
analysis_status: finiteNumber(item.analysis_result)
|
|
213
|
+
?? finiteNumber(item.analysis_status)
|
|
214
|
+
?? nonblank(item.analysis_result)
|
|
215
|
+
?? nonblank(item.analysis_status)
|
|
216
|
+
?? fallbackAsset?.analysis_status,
|
|
217
|
+
created_at: timestamp(item.created_at) ?? fallbackAsset?.created_at,
|
|
218
|
+
updated_at: timestamp(item.updated_at) ?? fallbackAsset?.updated_at
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
export function projectDamAssetDetail(value, fallback) {
|
|
222
|
+
const asset = normalizeAsset(value, fallback);
|
|
223
|
+
if (asset === undefined)
|
|
224
|
+
return undefined;
|
|
225
|
+
return compact({
|
|
226
|
+
asset_id: asset.asset_id,
|
|
227
|
+
title: asset.title,
|
|
228
|
+
description: asset.description,
|
|
229
|
+
format: asset.format,
|
|
230
|
+
type: asset.type,
|
|
231
|
+
size: asset.size,
|
|
232
|
+
url: asset.url,
|
|
233
|
+
cover_url: asset.cover_url,
|
|
234
|
+
width: asset.width,
|
|
235
|
+
height: asset.height,
|
|
236
|
+
duration: asset.duration,
|
|
237
|
+
repository_id: asset.repository_id,
|
|
238
|
+
repository_name: asset.repository_name,
|
|
239
|
+
folder_id: asset.folder_id,
|
|
240
|
+
folder_name: asset.folder_name,
|
|
241
|
+
tag_ids: asset.tag_ids,
|
|
242
|
+
analysis_status: asset.analysis_status,
|
|
243
|
+
created_at: asset.created_at,
|
|
244
|
+
updated_at: asset.updated_at
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
export function projectDamAssetSummary(value) {
|
|
248
|
+
const asset = normalizeAsset(value);
|
|
249
|
+
if (asset === undefined)
|
|
250
|
+
return undefined;
|
|
251
|
+
return compact({
|
|
252
|
+
asset_id: asset.asset_id,
|
|
253
|
+
title: asset.title,
|
|
254
|
+
format: asset.format,
|
|
255
|
+
type: asset.type,
|
|
256
|
+
size: asset.size,
|
|
257
|
+
url: asset.url,
|
|
258
|
+
cover_url: asset.cover_url,
|
|
259
|
+
width: asset.width,
|
|
260
|
+
height: asset.height,
|
|
261
|
+
repository_id: asset.repository_id,
|
|
262
|
+
folder_id: asset.folder_id,
|
|
263
|
+
folder_name: asset.folder_name,
|
|
264
|
+
tags: asset.tags,
|
|
265
|
+
tag_ids: asset.tag_ids,
|
|
266
|
+
analysis_status: asset.analysis_status,
|
|
267
|
+
created_at: asset.created_at,
|
|
268
|
+
updated_at: asset.updated_at
|
|
269
|
+
});
|
|
270
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
2
|
+
import { projectDamAssetDetail, projectDamAssetSummary } from "./asset-projection.js";
|
|
3
|
+
const contentFormats = {
|
|
4
|
+
image: ["gdpic", "gdimage"],
|
|
5
|
+
video: ["gdvideo"],
|
|
6
|
+
audio: ["gdaudio"],
|
|
7
|
+
font: ["gdfont"],
|
|
8
|
+
template: ["gdtemplate"],
|
|
9
|
+
file: ["gdfile", "gdimage", "gdslide"]
|
|
10
|
+
};
|
|
11
|
+
function record(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
13
|
+
? value
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
function nonblank(value) {
|
|
17
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
18
|
+
}
|
|
19
|
+
function finiteNumber(value) {
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
function repositories(response) {
|
|
23
|
+
const source = Array.isArray(response)
|
|
24
|
+
? response
|
|
25
|
+
: Array.isArray(record(response)?.data)
|
|
26
|
+
? record(response)?.data
|
|
27
|
+
: undefined;
|
|
28
|
+
if (source === undefined)
|
|
29
|
+
throw new RemoteRequestError();
|
|
30
|
+
return source.flatMap((value) => {
|
|
31
|
+
const item = record(value);
|
|
32
|
+
const id = nonblank(item?.repository_id) ?? nonblank(item?.id);
|
|
33
|
+
if (item === undefined || id === undefined)
|
|
34
|
+
return [];
|
|
35
|
+
const name = nonblank(item.name) ?? nonblank(item.title);
|
|
36
|
+
const type = finiteNumber(item.type);
|
|
37
|
+
return [{
|
|
38
|
+
id,
|
|
39
|
+
...(name === undefined ? {} : { name }),
|
|
40
|
+
...(type === undefined ? {} : { type })
|
|
41
|
+
}];
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function assetId(response) {
|
|
45
|
+
const root = record(response);
|
|
46
|
+
const nested = record(root?.data);
|
|
47
|
+
const id = nonblank(root?.id) ?? nonblank(nested?.id);
|
|
48
|
+
if (id === undefined)
|
|
49
|
+
throw new RemoteRequestError();
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
function responseRecords(response) {
|
|
53
|
+
const root = record(response);
|
|
54
|
+
const nested = record(root?.data);
|
|
55
|
+
return [root, nested].filter((item) => item !== undefined);
|
|
56
|
+
}
|
|
57
|
+
function searchItems(response) {
|
|
58
|
+
if (Array.isArray(response))
|
|
59
|
+
return response;
|
|
60
|
+
for (const source of responseRecords(response)) {
|
|
61
|
+
for (const key of ["data", "list", "items"]) {
|
|
62
|
+
if (Array.isArray(source[key]))
|
|
63
|
+
return source[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function firstValue(response, keys) {
|
|
69
|
+
for (const source of responseRecords(response).reverse()) {
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
if (source[key] !== undefined)
|
|
72
|
+
return source[key];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
function searchResult(response, input) {
|
|
78
|
+
const items = searchItems(response);
|
|
79
|
+
if (items === undefined)
|
|
80
|
+
throw new RemoteRequestError();
|
|
81
|
+
const assets = items.flatMap((item) => {
|
|
82
|
+
const asset = projectDamAssetSummary(item);
|
|
83
|
+
return asset === undefined ? [] : [asset];
|
|
84
|
+
});
|
|
85
|
+
const suppliedTotal = finiteNumber(firstValue(response, ["total", "count"]));
|
|
86
|
+
const total = suppliedTotal === undefined || suppliedTotal < 0 ? items.length : suppliedTotal;
|
|
87
|
+
const suppliedFinish = firstValue(response, ["finish"]);
|
|
88
|
+
const finish = typeof suppliedFinish === "boolean"
|
|
89
|
+
? suppliedFinish
|
|
90
|
+
: items.length >= total;
|
|
91
|
+
const cursor = nonblank(firstValue(response, ["query_id", "cursor"]));
|
|
92
|
+
return {
|
|
93
|
+
repository_ids: input.repositoryIds,
|
|
94
|
+
page_size: input.pageSize,
|
|
95
|
+
total,
|
|
96
|
+
finish,
|
|
97
|
+
...(!finish && cursor !== undefined ? { next_cursor: cursor } : {}),
|
|
98
|
+
assets
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function requestContext(input) {
|
|
102
|
+
return {
|
|
103
|
+
credential: input.access.credential,
|
|
104
|
+
organizationId: input.access.organizationId,
|
|
105
|
+
signal: input.signal
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function recycleBody(input) {
|
|
109
|
+
return {
|
|
110
|
+
repository_id: input.repositoryId,
|
|
111
|
+
file_ids: [input.assetId],
|
|
112
|
+
folder_ids: []
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function createDamApiAdapter(dependencies) {
|
|
116
|
+
return {
|
|
117
|
+
async listRepositories(input) {
|
|
118
|
+
const response = await dependencies.transport.getJson({
|
|
119
|
+
path: "/tb-dam/repositories",
|
|
120
|
+
...requestContext(input)
|
|
121
|
+
});
|
|
122
|
+
return repositories(response);
|
|
123
|
+
},
|
|
124
|
+
async search(input) {
|
|
125
|
+
const response = await dependencies.transport.postJson({
|
|
126
|
+
path: "/dam/asset/v2/search/simple",
|
|
127
|
+
body: {
|
|
128
|
+
keyword: input.keyword,
|
|
129
|
+
repository_ids: input.repositoryIds,
|
|
130
|
+
folder_ids: input.folderId === undefined ? [] : [input.folderId],
|
|
131
|
+
include_child_folder: input.includeChildFolder,
|
|
132
|
+
recycle: false,
|
|
133
|
+
page_size: input.pageSize,
|
|
134
|
+
page_num: 1,
|
|
135
|
+
page: 1,
|
|
136
|
+
sort: ["-top_time", "-updated_at"],
|
|
137
|
+
...(input.cursor === undefined
|
|
138
|
+
? {}
|
|
139
|
+
: { query_id: input.cursor, cursor: input.cursor }),
|
|
140
|
+
...(input.type === undefined
|
|
141
|
+
? {}
|
|
142
|
+
: { content_format_list: contentFormats[input.type] }),
|
|
143
|
+
...(input.assetFormats === undefined || input.assetFormats.length === 0
|
|
144
|
+
? {}
|
|
145
|
+
: { format_list: input.assetFormats }),
|
|
146
|
+
...(input.tagIds === undefined || input.tagIds.length === 0
|
|
147
|
+
? {}
|
|
148
|
+
: { tag_ids: input.tagIds })
|
|
149
|
+
},
|
|
150
|
+
...requestContext(input)
|
|
151
|
+
});
|
|
152
|
+
return searchResult(response, input);
|
|
153
|
+
},
|
|
154
|
+
async getAsset(input) {
|
|
155
|
+
const response = await dependencies.transport.getJson({
|
|
156
|
+
path: `/dam/asset/${encodeURIComponent(input.assetId)}`,
|
|
157
|
+
...requestContext(input)
|
|
158
|
+
});
|
|
159
|
+
const asset = projectDamAssetDetail(response, input.assetId);
|
|
160
|
+
if (asset === undefined)
|
|
161
|
+
throw new RemoteRequestError();
|
|
162
|
+
return asset;
|
|
163
|
+
},
|
|
164
|
+
async createAssetId(input) {
|
|
165
|
+
const response = await dependencies.transport.postJson({
|
|
166
|
+
path: "/tb-dam/asset/id",
|
|
167
|
+
body: {},
|
|
168
|
+
...requestContext(input)
|
|
169
|
+
});
|
|
170
|
+
return assetId(response);
|
|
171
|
+
},
|
|
172
|
+
async createMaterial(input) {
|
|
173
|
+
const response = await dependencies.transport.postJson({
|
|
174
|
+
path: "/dam/asset/createMaterial",
|
|
175
|
+
body: input.body,
|
|
176
|
+
...requestContext(input)
|
|
177
|
+
});
|
|
178
|
+
const asset = projectDamAssetDetail(response, input.fallback);
|
|
179
|
+
if (asset === undefined)
|
|
180
|
+
throw new RemoteRequestError();
|
|
181
|
+
return asset;
|
|
182
|
+
},
|
|
183
|
+
async getBatchStatus(input) {
|
|
184
|
+
const response = await dependencies.transport.getJson({
|
|
185
|
+
path: "/dam/asset/batch-status",
|
|
186
|
+
query: { ids: input.assetId },
|
|
187
|
+
...requestContext(input)
|
|
188
|
+
});
|
|
189
|
+
return projectDamAssetDetail(response, input.fallback) ?? null;
|
|
190
|
+
},
|
|
191
|
+
async recycle(input) {
|
|
192
|
+
await dependencies.transport.postJson({
|
|
193
|
+
path: "/dam/recycle-bin",
|
|
194
|
+
body: recycleBody(input),
|
|
195
|
+
...requestContext(input)
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
async deleteRecycled(input) {
|
|
199
|
+
await dependencies.transport.postJson({
|
|
200
|
+
path: "/dam/recycle-bin/delete",
|
|
201
|
+
body: recycleBody(input),
|
|
202
|
+
...requestContext(input)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import OSS from "ali-oss";
|
|
5
|
+
export class ObjectStorageUploadError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("素材上传失败。");
|
|
8
|
+
this.name = "ObjectStorageUploadError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function normalizeEndpoint(value) {
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(value.includes("://") ? value : `https://${value}`);
|
|
14
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
15
|
+
throw new Error("unsupported protocol");
|
|
16
|
+
}
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new ObjectStorageUploadError();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function endpointOrigin(value) {
|
|
24
|
+
return normalizeEndpoint(value).origin;
|
|
25
|
+
}
|
|
26
|
+
function endpointOrder(token) {
|
|
27
|
+
return [...new Set([
|
|
28
|
+
token.endpoint,
|
|
29
|
+
token.standbyEndpoint,
|
|
30
|
+
token.accelerateEndpoint
|
|
31
|
+
].filter((value) => value !== undefined).map(endpointOrigin))];
|
|
32
|
+
}
|
|
33
|
+
function errorText(error) {
|
|
34
|
+
if (!(error instanceof Error))
|
|
35
|
+
return "";
|
|
36
|
+
const code = "code" in error && typeof error.code === "string" ? error.code : "";
|
|
37
|
+
return `${code} ${error.message}`;
|
|
38
|
+
}
|
|
39
|
+
function isTransient(error) {
|
|
40
|
+
const text = errorText(error);
|
|
41
|
+
return text.includes("socket hang up")
|
|
42
|
+
|| text.includes("ECONNRESET")
|
|
43
|
+
|| text.includes("ETIMEDOUT")
|
|
44
|
+
|| text.includes("EPIPE");
|
|
45
|
+
}
|
|
46
|
+
function isUnsupportedAuthorization(error) {
|
|
47
|
+
return errorText(error).includes("Unsupported Authorization Type");
|
|
48
|
+
}
|
|
49
|
+
function sleepWithSignal(milliseconds, signal) {
|
|
50
|
+
signal.throwIfAborted();
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
signal.removeEventListener("abort", onAbort);
|
|
54
|
+
resolve();
|
|
55
|
+
}, milliseconds);
|
|
56
|
+
const onAbort = () => {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
reject(signal.reason);
|
|
59
|
+
};
|
|
60
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const defaultPutOss = async (input) => {
|
|
64
|
+
input.signal.throwIfAborted();
|
|
65
|
+
const client = new OSS({
|
|
66
|
+
accessKeyId: input.token.accessKeyId,
|
|
67
|
+
accessKeySecret: input.token.accessKeySecret,
|
|
68
|
+
stsToken: input.token.securityToken,
|
|
69
|
+
endpoint: input.endpoint,
|
|
70
|
+
region: input.token.region,
|
|
71
|
+
bucket: input.token.bucketName,
|
|
72
|
+
secure: true,
|
|
73
|
+
authorizationV4: input.authorizationV4
|
|
74
|
+
});
|
|
75
|
+
await client.put(input.token.objectName, input.filePath, {
|
|
76
|
+
headers: { "Content-Type": input.mediaType }
|
|
77
|
+
});
|
|
78
|
+
input.signal.throwIfAborted();
|
|
79
|
+
};
|
|
80
|
+
async function attemptOss(input, dependencies) {
|
|
81
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
82
|
+
input.signal.throwIfAborted();
|
|
83
|
+
try {
|
|
84
|
+
await dependencies.putOss(input);
|
|
85
|
+
input.signal.throwIfAborted();
|
|
86
|
+
return "success";
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (input.signal.aborted)
|
|
90
|
+
throw input.signal.reason;
|
|
91
|
+
if (!input.authorizationV4 && isUnsupportedAuthorization(error)) {
|
|
92
|
+
return "unsupported-authorization";
|
|
93
|
+
}
|
|
94
|
+
if (!isTransient(error))
|
|
95
|
+
throw new ObjectStorageUploadError();
|
|
96
|
+
if (attempt === 1)
|
|
97
|
+
return "transient-exhausted";
|
|
98
|
+
await dependencies.sleep(300, input.signal);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return "transient-exhausted";
|
|
102
|
+
}
|
|
103
|
+
function isHuaweiObs(endpoint) {
|
|
104
|
+
const hostname = normalizeEndpoint(endpoint).hostname;
|
|
105
|
+
return /(^|\.)obs\.[^.]+\.myhuaweicloud\.com$/i.test(hostname);
|
|
106
|
+
}
|
|
107
|
+
function encodedObjectPath(objectName) {
|
|
108
|
+
return objectName.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
|
|
109
|
+
}
|
|
110
|
+
async function uploadObs(input, dependencies) {
|
|
111
|
+
input.signal.throwIfAborted();
|
|
112
|
+
const file = await stat(input.filePath);
|
|
113
|
+
input.signal.throwIfAborted();
|
|
114
|
+
const endpoint = normalizeEndpoint(input.endpoint);
|
|
115
|
+
const objectName = input.token.objectName.replace(/^\/+/, "");
|
|
116
|
+
endpoint.hostname = `${input.token.bucketName}.${endpoint.hostname}`;
|
|
117
|
+
endpoint.pathname = `/${encodedObjectPath(objectName)}`;
|
|
118
|
+
const date = dependencies.now().toUTCString();
|
|
119
|
+
const stringToSign = [
|
|
120
|
+
"PUT",
|
|
121
|
+
"",
|
|
122
|
+
input.mediaType,
|
|
123
|
+
date,
|
|
124
|
+
`x-obs-security-token:${input.token.securityToken}`,
|
|
125
|
+
`/${input.token.bucketName}/${objectName}`
|
|
126
|
+
].join("\n");
|
|
127
|
+
const signature = createHmac("sha1", input.token.accessKeySecret)
|
|
128
|
+
.update(stringToSign)
|
|
129
|
+
.digest("base64");
|
|
130
|
+
const stream = createReadStream(input.filePath);
|
|
131
|
+
try {
|
|
132
|
+
const response = await dependencies.fetch(endpoint, {
|
|
133
|
+
method: "PUT",
|
|
134
|
+
headers: {
|
|
135
|
+
Authorization: `OBS ${input.token.accessKeyId}:${signature}`,
|
|
136
|
+
Date: date,
|
|
137
|
+
"Content-Type": input.mediaType,
|
|
138
|
+
"Content-Length": String(file.size),
|
|
139
|
+
"x-obs-security-token": input.token.securityToken
|
|
140
|
+
},
|
|
141
|
+
body: stream,
|
|
142
|
+
signal: input.signal,
|
|
143
|
+
duplex: "half"
|
|
144
|
+
});
|
|
145
|
+
input.signal.throwIfAborted();
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
stream.destroy();
|
|
148
|
+
throw new ObjectStorageUploadError();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
stream.destroy();
|
|
153
|
+
if (input.signal.aborted)
|
|
154
|
+
throw input.signal.reason;
|
|
155
|
+
if (error instanceof ObjectStorageUploadError)
|
|
156
|
+
throw error;
|
|
157
|
+
throw new ObjectStorageUploadError();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function createObjectStorageUploader(options = {}) {
|
|
161
|
+
const dependencies = {
|
|
162
|
+
putOss: options.putOss ?? defaultPutOss,
|
|
163
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
164
|
+
sleep: options.sleep ?? sleepWithSignal,
|
|
165
|
+
now: options.now ?? (() => new Date())
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
async upload(input) {
|
|
169
|
+
input.signal.throwIfAborted();
|
|
170
|
+
const endpoints = endpointOrder(input.token);
|
|
171
|
+
if (endpoints.length === 0)
|
|
172
|
+
throw new ObjectStorageUploadError();
|
|
173
|
+
if (isHuaweiObs(endpoints[0])) {
|
|
174
|
+
await uploadObs({ ...input, endpoint: endpoints[0] }, dependencies);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const endpoint of endpoints) {
|
|
178
|
+
const base = { ...input, endpoint, authorizationV4: false };
|
|
179
|
+
const v1 = await attemptOss(base, dependencies);
|
|
180
|
+
if (v1 === "success")
|
|
181
|
+
return;
|
|
182
|
+
if (v1 !== "unsupported-authorization")
|
|
183
|
+
continue;
|
|
184
|
+
const v4 = await attemptOss({ ...base, authorizationV4: true }, dependencies);
|
|
185
|
+
if (v4 === "success")
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
throw new ObjectStorageUploadError();
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|