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,224 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import sharp from "sharp";
|
|
5
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
6
|
+
import { projectDamAssetDetail } from "./asset-projection.js";
|
|
7
|
+
const imageFormats = new Set([
|
|
8
|
+
"png", "jpg", "jpeg", "gif", "svg", "webp", "jfif", "bmp", "tiff", "tif", "heic", "avif"
|
|
9
|
+
]);
|
|
10
|
+
const videoFormats = new Set([
|
|
11
|
+
"mp4", "avi", "flv", "mov", "3gp", "m3u8", "mpg", "asf", "wmv", "mkv", "ts", "webm", "mxf"
|
|
12
|
+
]);
|
|
13
|
+
const audioFormats = new Set(["mp3", "wav", "ogg", "flac"]);
|
|
14
|
+
const fontFormats = new Set(["ttf", "otf"]);
|
|
15
|
+
const designFormats = new Set(["psd", "ai", "sketch", "pdf"]);
|
|
16
|
+
const slideFormats = new Set(["ppt", "pptx"]);
|
|
17
|
+
function storageFormat(format) {
|
|
18
|
+
const normalized = format.toLowerCase();
|
|
19
|
+
if (imageFormats.has(normalized))
|
|
20
|
+
return "gdpic";
|
|
21
|
+
if (videoFormats.has(normalized))
|
|
22
|
+
return "gdvideo";
|
|
23
|
+
if (audioFormats.has(normalized))
|
|
24
|
+
return "gdaudio";
|
|
25
|
+
if (fontFormats.has(normalized))
|
|
26
|
+
return "gdfont";
|
|
27
|
+
if (designFormats.has(normalized))
|
|
28
|
+
return "gdimage";
|
|
29
|
+
if (slideFormats.has(normalized))
|
|
30
|
+
return "gdslide";
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
function publicType(format) {
|
|
34
|
+
if (format === "gdpic" || format === "gdimage")
|
|
35
|
+
return "image";
|
|
36
|
+
if (format === "gdvideo")
|
|
37
|
+
return "video";
|
|
38
|
+
if (format === "gdaudio")
|
|
39
|
+
return "audio";
|
|
40
|
+
if (format === "gdfont")
|
|
41
|
+
return "font";
|
|
42
|
+
if (format === "gdtemplate")
|
|
43
|
+
return "template";
|
|
44
|
+
return "file";
|
|
45
|
+
}
|
|
46
|
+
async function hashes(filePath, signal) {
|
|
47
|
+
signal.throwIfAborted();
|
|
48
|
+
const md5 = createHash("md5");
|
|
49
|
+
const sha1 = createHash("sha1");
|
|
50
|
+
const stream = createReadStream(filePath, { signal });
|
|
51
|
+
try {
|
|
52
|
+
for await (const chunk of stream) {
|
|
53
|
+
signal.throwIfAborted();
|
|
54
|
+
md5.update(chunk);
|
|
55
|
+
sha1.update(chunk);
|
|
56
|
+
}
|
|
57
|
+
signal.throwIfAborted();
|
|
58
|
+
return { md5: md5.digest("hex"), sha1: sha1.digest("hex") };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (signal.aborted)
|
|
62
|
+
throw signal.reason;
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function filenameTitle(filename) {
|
|
67
|
+
const withoutExtension = filename.replace(/\.[^.]+$/u, "");
|
|
68
|
+
return withoutExtension === "" ? filename : withoutExtension;
|
|
69
|
+
}
|
|
70
|
+
function terminalAnalysisStatus(value) {
|
|
71
|
+
const normalized = typeof value === "string" ? Number(value) : value;
|
|
72
|
+
return normalized === 2 || normalized === 3 || normalized === 4;
|
|
73
|
+
}
|
|
74
|
+
async function defaultSleep(milliseconds, signal) {
|
|
75
|
+
signal.throwIfAborted();
|
|
76
|
+
try {
|
|
77
|
+
await delay(milliseconds, undefined, { signal });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (signal.aborted)
|
|
81
|
+
throw signal.reason;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function createDamRegisteredUploader(dependencies) {
|
|
86
|
+
const now = dependencies.now ?? (() => new Date());
|
|
87
|
+
const sleep = dependencies.sleep ?? defaultSleep;
|
|
88
|
+
const imageMetadata = dependencies.imageMetadata
|
|
89
|
+
?? (async (filePath) => {
|
|
90
|
+
const metadata = await sharp(filePath).metadata();
|
|
91
|
+
return { width: metadata.width, height: metadata.height };
|
|
92
|
+
});
|
|
93
|
+
async function localMetadata(file, signal) {
|
|
94
|
+
if (!file.mediaType.startsWith("image/"))
|
|
95
|
+
return {};
|
|
96
|
+
try {
|
|
97
|
+
const metadata = await imageMetadata(file.filePath);
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
return {
|
|
100
|
+
...(typeof metadata.width === "number" && Number.isFinite(metadata.width)
|
|
101
|
+
? { width: metadata.width }
|
|
102
|
+
: {}),
|
|
103
|
+
...(typeof metadata.height === "number" && Number.isFinite(metadata.height)
|
|
104
|
+
? { height: metadata.height }
|
|
105
|
+
: {})
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
if (signal.aborted)
|
|
110
|
+
throw signal.reason;
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
async upload(input) {
|
|
116
|
+
try {
|
|
117
|
+
input.signal.throwIfAborted();
|
|
118
|
+
const [digest, metadata] = await Promise.all([
|
|
119
|
+
hashes(input.file.filePath, input.signal),
|
|
120
|
+
localMetadata(input.file, input.signal)
|
|
121
|
+
]);
|
|
122
|
+
input.signal.throwIfAborted();
|
|
123
|
+
const contentId = await dependencies.api.createAssetId({
|
|
124
|
+
access: input.access,
|
|
125
|
+
signal: input.signal
|
|
126
|
+
});
|
|
127
|
+
const stored = await dependencies.storage.upload({
|
|
128
|
+
file: input.file,
|
|
129
|
+
contentId
|
|
130
|
+
}, input.access, input.signal);
|
|
131
|
+
const format = input.file.format.toLowerCase();
|
|
132
|
+
const damFormat = storageFormat(format);
|
|
133
|
+
const title = input.input.title ?? filenameTitle(input.file.filename);
|
|
134
|
+
const folderId = input.input.folderId ?? "0";
|
|
135
|
+
const preview = input.file.mediaType.startsWith("image/")
|
|
136
|
+
? {
|
|
137
|
+
url: stored.url,
|
|
138
|
+
...metadata,
|
|
139
|
+
format
|
|
140
|
+
}
|
|
141
|
+
: {};
|
|
142
|
+
const body = {
|
|
143
|
+
folder_id: folderId,
|
|
144
|
+
repository_id: input.repository.id,
|
|
145
|
+
second_transmission: false,
|
|
146
|
+
storage_format: damFormat,
|
|
147
|
+
category_ids: [],
|
|
148
|
+
asset_info: {
|
|
149
|
+
id: contentId,
|
|
150
|
+
origin_url: stored.url,
|
|
151
|
+
size: input.file.size,
|
|
152
|
+
title,
|
|
153
|
+
md5: digest.md5,
|
|
154
|
+
sha1: digest.sha1,
|
|
155
|
+
mime_type: input.file.mediaType,
|
|
156
|
+
format,
|
|
157
|
+
preview
|
|
158
|
+
},
|
|
159
|
+
...(input.input.tagIds === undefined
|
|
160
|
+
? {}
|
|
161
|
+
: { tag_ids: [...input.input.tagIds] })
|
|
162
|
+
};
|
|
163
|
+
const fallback = projectDamAssetDetail({
|
|
164
|
+
id: contentId,
|
|
165
|
+
title,
|
|
166
|
+
format,
|
|
167
|
+
type: publicType(damFormat),
|
|
168
|
+
origin_info: {
|
|
169
|
+
url: stored.url,
|
|
170
|
+
size: input.file.size,
|
|
171
|
+
format
|
|
172
|
+
},
|
|
173
|
+
preview_info: preview,
|
|
174
|
+
repository_info: {
|
|
175
|
+
id: input.repository.id,
|
|
176
|
+
...(input.repository.name === undefined ? {} : { name: input.repository.name })
|
|
177
|
+
},
|
|
178
|
+
folder_info: { id: folderId },
|
|
179
|
+
...(input.input.tagIds === undefined
|
|
180
|
+
? {}
|
|
181
|
+
: { tag_ids: [...input.input.tagIds] })
|
|
182
|
+
});
|
|
183
|
+
if (fallback === undefined)
|
|
184
|
+
throw new RemoteRequestError();
|
|
185
|
+
let latest = await dependencies.api.createMaterial({
|
|
186
|
+
body,
|
|
187
|
+
fallback,
|
|
188
|
+
access: input.access,
|
|
189
|
+
signal: input.signal
|
|
190
|
+
});
|
|
191
|
+
if (input.input.waitAnalysis !== true || terminalAnalysisStatus(latest.analysis_status)) {
|
|
192
|
+
return latest;
|
|
193
|
+
}
|
|
194
|
+
const deadline = now().getTime() + 180_000;
|
|
195
|
+
while (true) {
|
|
196
|
+
input.signal.throwIfAborted();
|
|
197
|
+
const current = await dependencies.api.getBatchStatus({
|
|
198
|
+
assetId: latest.asset_id,
|
|
199
|
+
fallback: latest,
|
|
200
|
+
access: input.access,
|
|
201
|
+
signal: input.signal
|
|
202
|
+
});
|
|
203
|
+
if (current !== null)
|
|
204
|
+
latest = current;
|
|
205
|
+
if (terminalAnalysisStatus(latest.analysis_status))
|
|
206
|
+
return latest;
|
|
207
|
+
const remaining = deadline - now().getTime();
|
|
208
|
+
if (remaining <= 0) {
|
|
209
|
+
dependencies.warn("素材分析仍在处理中,已返回当前结果。");
|
|
210
|
+
return latest;
|
|
211
|
+
}
|
|
212
|
+
await sleep(Math.min(3000, remaining), input.signal);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (input.signal.aborted)
|
|
217
|
+
throw input.signal.reason;
|
|
218
|
+
if (error instanceof RemoteRequestError)
|
|
219
|
+
throw error;
|
|
220
|
+
throw new RemoteRequestError();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
2
|
+
import { inspectUploadFile } from "../../platform/safe-upload-file.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function requiredString(record, key) {
|
|
7
|
+
const value = record[key];
|
|
8
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
9
|
+
throw new RemoteRequestError();
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function optionalString(record, key) {
|
|
13
|
+
const value = record[key];
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
17
|
+
throw new RemoteRequestError();
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function parseToken(response) {
|
|
21
|
+
if (!isRecord(response))
|
|
22
|
+
throw new RemoteRequestError();
|
|
23
|
+
const record = "data" in response ? response.data : response;
|
|
24
|
+
if (!isRecord(record))
|
|
25
|
+
throw new RemoteRequestError();
|
|
26
|
+
const standbyEndpoint = optionalString(record, "standby_endpoint");
|
|
27
|
+
const accelerateEndpoint = optionalString(record, "accelerate_endpoint");
|
|
28
|
+
const authKey = optionalString(record, "auth_key");
|
|
29
|
+
return {
|
|
30
|
+
securityToken: requiredString(record, "security_token"),
|
|
31
|
+
accessKeyId: requiredString(record, "access_key_id"),
|
|
32
|
+
accessKeySecret: requiredString(record, "access_key_secret"),
|
|
33
|
+
endpoint: requiredString(record, "endpoint"),
|
|
34
|
+
...(standbyEndpoint === undefined ? {} : { standbyEndpoint }),
|
|
35
|
+
...(accelerateEndpoint === undefined ? {} : { accelerateEndpoint }),
|
|
36
|
+
region: requiredString(record, "region"),
|
|
37
|
+
bucketName: requiredString(record, "bucket_name"),
|
|
38
|
+
objectName: requiredString(record, "object_name"),
|
|
39
|
+
host: requiredString(record, "host"),
|
|
40
|
+
...(authKey === undefined ? {} : { authKey })
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function publicAssetUrl(token) {
|
|
44
|
+
try {
|
|
45
|
+
const normalizedHost = token.host.includes("://") ? token.host : `https://${token.host}`;
|
|
46
|
+
const url = new URL(normalizedHost);
|
|
47
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:")
|
|
48
|
+
|| url.username !== ""
|
|
49
|
+
|| url.password !== "") {
|
|
50
|
+
throw new RemoteRequestError();
|
|
51
|
+
}
|
|
52
|
+
url.search = "";
|
|
53
|
+
url.hash = "";
|
|
54
|
+
if (!url.pathname.endsWith("/"))
|
|
55
|
+
url.pathname += "/";
|
|
56
|
+
url.pathname += token.objectName
|
|
57
|
+
.replace(/^\/+/, "")
|
|
58
|
+
.split("/")
|
|
59
|
+
.map(encodeURIComponent)
|
|
60
|
+
.join("/");
|
|
61
|
+
if (token.authKey !== undefined)
|
|
62
|
+
url.searchParams.set("auth_key", token.authKey);
|
|
63
|
+
return url.href;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof RemoteRequestError)
|
|
67
|
+
throw error;
|
|
68
|
+
throw new RemoteRequestError();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function storageToken(token) {
|
|
72
|
+
return {
|
|
73
|
+
securityToken: token.securityToken,
|
|
74
|
+
accessKeyId: token.accessKeyId,
|
|
75
|
+
accessKeySecret: token.accessKeySecret,
|
|
76
|
+
endpoint: token.endpoint,
|
|
77
|
+
...(token.standbyEndpoint === undefined
|
|
78
|
+
? {}
|
|
79
|
+
: { standbyEndpoint: token.standbyEndpoint }),
|
|
80
|
+
...(token.accelerateEndpoint === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { accelerateEndpoint: token.accelerateEndpoint }),
|
|
83
|
+
region: token.region,
|
|
84
|
+
bucketName: token.bucketName,
|
|
85
|
+
objectName: token.objectName
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function createDamStorageUploader(dependencies) {
|
|
89
|
+
return {
|
|
90
|
+
inspect(input, signal) {
|
|
91
|
+
return inspectUploadFile({ ...input, signal });
|
|
92
|
+
},
|
|
93
|
+
async upload(input, access, signal) {
|
|
94
|
+
signal.throwIfAborted();
|
|
95
|
+
let token;
|
|
96
|
+
let url;
|
|
97
|
+
try {
|
|
98
|
+
const response = await dependencies.transport.postJson({
|
|
99
|
+
path: "/tb-dam/asset/upload/tokens",
|
|
100
|
+
body: {
|
|
101
|
+
...(input.contentId === undefined ? {} : { content_id: input.contentId }),
|
|
102
|
+
format: input.file.format
|
|
103
|
+
},
|
|
104
|
+
credential: access.credential,
|
|
105
|
+
organizationId: access.organizationId,
|
|
106
|
+
signal
|
|
107
|
+
});
|
|
108
|
+
signal.throwIfAborted();
|
|
109
|
+
token = parseToken(response);
|
|
110
|
+
url = publicAssetUrl(token);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (signal.aborted)
|
|
114
|
+
throw signal.reason;
|
|
115
|
+
if (error instanceof RemoteRequestError)
|
|
116
|
+
throw error;
|
|
117
|
+
throw new RemoteRequestError();
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await dependencies.storage.upload({
|
|
121
|
+
token: storageToken(token),
|
|
122
|
+
filePath: input.file.filePath,
|
|
123
|
+
mediaType: input.file.mediaType,
|
|
124
|
+
signal
|
|
125
|
+
});
|
|
126
|
+
signal.throwIfAborted();
|
|
127
|
+
return {
|
|
128
|
+
...input.file,
|
|
129
|
+
url
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (signal.aborted)
|
|
134
|
+
throw signal.reason;
|
|
135
|
+
if (error instanceof RemoteRequestError)
|
|
136
|
+
throw error;
|
|
137
|
+
throw new RemoteRequestError();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createDamTransientUploader(dependencies) {
|
|
2
|
+
return {
|
|
3
|
+
async upload(input, access, signal) {
|
|
4
|
+
const file = await dependencies.storageUpload.inspect({
|
|
5
|
+
url: input.url,
|
|
6
|
+
...(input.mediaType === undefined ? {} : { claimedMediaType: input.mediaType })
|
|
7
|
+
}, signal);
|
|
8
|
+
const stored = await dependencies.storageUpload.upload({ file }, access, signal);
|
|
9
|
+
return {
|
|
10
|
+
url: stored.url,
|
|
11
|
+
mediaType: stored.mediaType,
|
|
12
|
+
filename: input.filename ?? stored.filename,
|
|
13
|
+
fromUserUpload: true
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { RemoteRequestError } from "../../platform/signed-http-transport.js";
|
|
4
|
+
export class DamInputError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("DAM 输入不可用。");
|
|
7
|
+
this.name = "DamInputError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function invalid() {
|
|
11
|
+
throw new DamInputError();
|
|
12
|
+
}
|
|
13
|
+
function nonblank(value) {
|
|
14
|
+
return value !== undefined && value.trim() !== "";
|
|
15
|
+
}
|
|
16
|
+
function assertPageSize(value) {
|
|
17
|
+
if (value !== undefined && (!Number.isInteger(value) || value < 1 || value > 100))
|
|
18
|
+
invalid();
|
|
19
|
+
}
|
|
20
|
+
function assertAssetId(value) {
|
|
21
|
+
if (value.trim() === "" || /^https?:\/\//iu.test(value))
|
|
22
|
+
invalid();
|
|
23
|
+
}
|
|
24
|
+
function assertRepositorySelection(input) {
|
|
25
|
+
if (input.repositoryId !== undefined && !nonblank(input.repositoryId))
|
|
26
|
+
invalid();
|
|
27
|
+
if (input.repositoryId !== undefined && input.allRepositories === true)
|
|
28
|
+
invalid();
|
|
29
|
+
}
|
|
30
|
+
function searchRequest(input, keyword, repositoryIds) {
|
|
31
|
+
return {
|
|
32
|
+
keyword,
|
|
33
|
+
repositoryIds,
|
|
34
|
+
includeChildFolder: input.includeChildFolder ?? true,
|
|
35
|
+
pageSize: input.pageSize ?? 20,
|
|
36
|
+
...(input.folderId === undefined ? {} : { folderId: input.folderId }),
|
|
37
|
+
...(input.type === undefined ? {} : { type: input.type }),
|
|
38
|
+
...(input.assetFormats === undefined ? {} : { assetFormats: [...input.assetFormats] }),
|
|
39
|
+
...(input.tagIds === undefined ? {} : { tagIds: [...input.tagIds] }),
|
|
40
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor })
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function createDamUseCases(dependencies) {
|
|
44
|
+
const validate = dependencies.validate;
|
|
45
|
+
async function resolveRepositories(input, access, signal) {
|
|
46
|
+
assertRepositorySelection(input);
|
|
47
|
+
if (input.repositoryId !== undefined)
|
|
48
|
+
return [{ id: input.repositoryId }];
|
|
49
|
+
signal.throwIfAborted();
|
|
50
|
+
const available = await dependencies.api.listRepositories({ access, signal });
|
|
51
|
+
signal.throwIfAborted();
|
|
52
|
+
if (available.length === 0)
|
|
53
|
+
throw new RemoteRequestError();
|
|
54
|
+
if (input.allRepositories === true)
|
|
55
|
+
return available;
|
|
56
|
+
const selected = available.find((repository) => repository.type === 1) ?? available[0];
|
|
57
|
+
if (selected === undefined)
|
|
58
|
+
throw new RemoteRequestError();
|
|
59
|
+
return [selected];
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
async list({ input, access, signal }) {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
assertPageSize(input.pageSize);
|
|
65
|
+
const repositories = await resolveRepositories(input, access, signal);
|
|
66
|
+
const result = await dependencies.api.search({
|
|
67
|
+
...searchRequest(input, "", repositories.map((repository) => repository.id)),
|
|
68
|
+
access,
|
|
69
|
+
signal
|
|
70
|
+
});
|
|
71
|
+
signal.throwIfAborted();
|
|
72
|
+
validate.list(result);
|
|
73
|
+
return result;
|
|
74
|
+
},
|
|
75
|
+
async search({ input, access, signal }) {
|
|
76
|
+
signal.throwIfAborted();
|
|
77
|
+
if (!nonblank(input.query))
|
|
78
|
+
invalid();
|
|
79
|
+
assertPageSize(input.pageSize);
|
|
80
|
+
const repositories = await resolveRepositories(input, access, signal);
|
|
81
|
+
const result = await dependencies.api.search({
|
|
82
|
+
...searchRequest(input, input.query, repositories.map((repository) => repository.id)),
|
|
83
|
+
access,
|
|
84
|
+
signal
|
|
85
|
+
});
|
|
86
|
+
signal.throwIfAborted();
|
|
87
|
+
validate.search(result);
|
|
88
|
+
return result;
|
|
89
|
+
},
|
|
90
|
+
async get({ input, access, signal }) {
|
|
91
|
+
signal.throwIfAborted();
|
|
92
|
+
assertAssetId(input.asset_id);
|
|
93
|
+
const result = await dependencies.api.getAsset({
|
|
94
|
+
assetId: input.asset_id,
|
|
95
|
+
access,
|
|
96
|
+
signal
|
|
97
|
+
});
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
validate.get(result);
|
|
100
|
+
return result;
|
|
101
|
+
},
|
|
102
|
+
async upload({ input, access, signal }) {
|
|
103
|
+
signal.throwIfAborted();
|
|
104
|
+
if (!nonblank(input.file))
|
|
105
|
+
invalid();
|
|
106
|
+
const file = await dependencies.storage.inspect({
|
|
107
|
+
url: pathToFileURL(resolve(process.cwd(), input.file)).href,
|
|
108
|
+
...(input.allowSensitivePath === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { allowSensitivePath: input.allowSensitivePath })
|
|
111
|
+
}, signal);
|
|
112
|
+
signal.throwIfAborted();
|
|
113
|
+
const [repository] = await resolveRepositories({
|
|
114
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
115
|
+
}, access, signal);
|
|
116
|
+
if (repository === undefined)
|
|
117
|
+
throw new RemoteRequestError();
|
|
118
|
+
const result = await dependencies.uploader.upload({
|
|
119
|
+
input,
|
|
120
|
+
file,
|
|
121
|
+
repository,
|
|
122
|
+
access,
|
|
123
|
+
signal
|
|
124
|
+
});
|
|
125
|
+
signal.throwIfAborted();
|
|
126
|
+
validate.upload(result);
|
|
127
|
+
return result;
|
|
128
|
+
},
|
|
129
|
+
async delete({ input, access, signal }) {
|
|
130
|
+
signal.throwIfAborted();
|
|
131
|
+
assertAssetId(input.asset_id);
|
|
132
|
+
const [repository] = await resolveRepositories({
|
|
133
|
+
...(input.repositoryId === undefined ? {} : { repositoryId: input.repositoryId })
|
|
134
|
+
}, access, signal);
|
|
135
|
+
if (repository === undefined)
|
|
136
|
+
throw new RemoteRequestError();
|
|
137
|
+
const request = {
|
|
138
|
+
repositoryId: repository.id,
|
|
139
|
+
assetId: input.asset_id,
|
|
140
|
+
access,
|
|
141
|
+
signal
|
|
142
|
+
};
|
|
143
|
+
await dependencies.api.recycle(request);
|
|
144
|
+
signal.throwIfAborted();
|
|
145
|
+
if (input.permanent === true)
|
|
146
|
+
await dependencies.api.deleteRecycled(request);
|
|
147
|
+
signal.throwIfAborted();
|
|
148
|
+
validate.delete(null);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
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 function createEditorBridgeClient(options) {
|
|
10
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
11
|
+
const baseUrl = `http://127.0.0.1:${options.state.port}`;
|
|
12
|
+
return {
|
|
13
|
+
async health(signal) {
|
|
14
|
+
const value = await request("/health", "GET", undefined, signal);
|
|
15
|
+
if (!isRecord(value) ||
|
|
16
|
+
!hasExactKeys(value, ["status", "pageConnected", "protocolVersion"]) ||
|
|
17
|
+
value.status !== "ok" ||
|
|
18
|
+
typeof value.pageConnected !== "boolean" ||
|
|
19
|
+
typeof value.protocolVersion !== "number") {
|
|
20
|
+
throw invalidResponse();
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
status: "ok",
|
|
24
|
+
pageConnected: value.pageConnected,
|
|
25
|
+
protocolVersion: value.protocolVersion
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
async rpc(method, payload, signal) {
|
|
29
|
+
const value = await request("/rpc", "POST", { method, ...(payload === undefined ? {} : { payload }) }, signal);
|
|
30
|
+
if (!isRecord(value) ||
|
|
31
|
+
!hasExactKeys(value, ["result"]) ||
|
|
32
|
+
!("result" in value)) {
|
|
33
|
+
throw invalidResponse();
|
|
34
|
+
}
|
|
35
|
+
return value.result;
|
|
36
|
+
},
|
|
37
|
+
async disconnect(signal) {
|
|
38
|
+
const value = await request("/disconnect", "POST", {}, signal);
|
|
39
|
+
if (!isRecord(value) ||
|
|
40
|
+
!hasExactKeys(value, ["status"]) ||
|
|
41
|
+
value.status !== "disconnected") {
|
|
42
|
+
throw invalidResponse();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
async function request(pathname, method, body, signal) {
|
|
47
|
+
const response = await fetch(`${baseUrl}${pathname}`, {
|
|
48
|
+
method,
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${options.state.token}`,
|
|
51
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" })
|
|
52
|
+
},
|
|
53
|
+
redirect: "error",
|
|
54
|
+
signal,
|
|
55
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) })
|
|
56
|
+
});
|
|
57
|
+
let value;
|
|
58
|
+
try {
|
|
59
|
+
value = await response.json();
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw invalidResponse();
|
|
63
|
+
}
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const error = readStructuredError(value);
|
|
66
|
+
if (!error)
|
|
67
|
+
throw invalidResponse();
|
|
68
|
+
throw new EditorBridgeError(error.code, error.message);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function readStructuredError(value) {
|
|
74
|
+
if (!isRecord(value) || !isRecord(value.error))
|
|
75
|
+
return null;
|
|
76
|
+
const { code, message } = value.error;
|
|
77
|
+
if (typeof code !== "string" ||
|
|
78
|
+
!/^[A-Z][A-Z0-9_]*$/.test(code) ||
|
|
79
|
+
typeof message !== "string" ||
|
|
80
|
+
message.trim().length === 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return { code, message };
|
|
84
|
+
}
|
|
85
|
+
function invalidResponse() {
|
|
86
|
+
return new EditorBridgeError("EDITOR_BRIDGE_INVALID_RESPONSE", "Editor Bridge 返回了无效响应。");
|
|
87
|
+
}
|
|
88
|
+
function isRecord(value) {
|
|
89
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
function hasExactKeys(value, keys) {
|
|
92
|
+
const actual = Object.keys(value).sort();
|
|
93
|
+
const expected = [...keys].sort();
|
|
94
|
+
return actual.length === expected.length &&
|
|
95
|
+
actual.every((key, index) => key === expected[index]);
|
|
96
|
+
}
|