@lingjingai/anime-cli-pre 0.1.5
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/bin/anime-cli.js +7 -0
- package/package.json +38 -0
- package/src/anime-cli.js +785 -0
- package/src/command-helpers.js +187 -0
- package/src/commands/asset.js +218 -0
- package/src/commands/script.js +26 -0
- package/src/commands/video.js +29 -0
package/bin/anime-cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lingjingai/anime-cli-pre",
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "Standard CLI for Anime Workbench script, asset, and video output controllers.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"anime-cli": "./bin/anime-cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test",
|
|
14
|
+
"prepack": "node -e \"const p=require('./package.json'); require('./src/anime-cli').preparePackageEnv((p.config && p.config.animeCliEnv) || 'pre')\"",
|
|
15
|
+
"postpack": "node -e \"require('./src/anime-cli').preparePackageEnv('pre')\"",
|
|
16
|
+
"pack:dev": "node -e \"require('./src/anime-cli').packForEnv('dev', 'dist/dev')\"",
|
|
17
|
+
"pack:pre": "node -e \"require('./src/anime-cli').packForEnv('pre', 'dist/pre')\"",
|
|
18
|
+
"pack:prod": "node -e \"require('./src/anime-cli').packForEnv('prod', 'dist/prod')\""
|
|
19
|
+
},
|
|
20
|
+
"config": {
|
|
21
|
+
"animeCliEnv": "pre",
|
|
22
|
+
"animeCliBaseUrls": {
|
|
23
|
+
"dev": "https://animeworkbench-dev.lingjingai.cn/api/anime/workbench",
|
|
24
|
+
"pre": "https://animeworkbench-pre.lingjingai.cn/api/anime/workbench",
|
|
25
|
+
"prod": "https://animeworkbench.lingjingai.cn/api/anime/workbench"
|
|
26
|
+
},
|
|
27
|
+
"animeCliPackageNames": {
|
|
28
|
+
"dev": "@lingjingai/anime-cli-dev",
|
|
29
|
+
"pre": "@lingjingai/anime-cli-pre",
|
|
30
|
+
"prod": "@lingjingai/anime-cli"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"private": false,
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/anime-cli.js
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const pkg = require('../package.json');
|
|
5
|
+
const scriptCommands = require('./commands/script').commands;
|
|
6
|
+
const assetCommands = require('./commands/asset').commands;
|
|
7
|
+
const videoCommands = require('./commands/video').commands;
|
|
8
|
+
|
|
9
|
+
const VERSION = pkg.version;
|
|
10
|
+
const WORKBENCH_CONTEXT_PATH = '/api/anime/workbench';
|
|
11
|
+
const DEFAULT_PACK_ENV = 'pre';
|
|
12
|
+
const DEFAULT_BASE_URL_BY_ENV = {
|
|
13
|
+
dev: 'https://animeworkbench-dev.lingjingai.cn/api/anime/workbench',
|
|
14
|
+
pre: 'https://animeworkbench-pre.lingjingai.cn/api/anime/workbench',
|
|
15
|
+
prod: 'https://animeworkbench.lingjingai.cn/api/anime/workbench',
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_PACKAGE_NAME_BY_ENV = {
|
|
18
|
+
dev: '@lingjingai/anime-cli-dev',
|
|
19
|
+
pre: '@lingjingai/anime-cli-pre',
|
|
20
|
+
prod: '@lingjingai/anime-cli',
|
|
21
|
+
};
|
|
22
|
+
const BASE_URL_BY_ENV = Object.assign({}, DEFAULT_BASE_URL_BY_ENV, (pkg.config && pkg.config.animeCliBaseUrls) || {});
|
|
23
|
+
const PACKAGE_NAME_BY_ENV = Object.assign({}, DEFAULT_PACKAGE_NAME_BY_ENV, (pkg.config && pkg.config.animeCliPackageNames) || {});
|
|
24
|
+
const PACKAGE_DEFAULT_CLI_ENV = normalizeCliEnv((pkg.config && pkg.config.animeCliEnv) || DEFAULT_PACK_ENV);
|
|
25
|
+
const DEFAULT_BASE_URL = baseUrlForEnv(PACKAGE_DEFAULT_CLI_ENV);
|
|
26
|
+
const SUCCESS_CODE = 200;
|
|
27
|
+
const DOMAINS = ['system', 'script', 'asset', 'video'];
|
|
28
|
+
const OUTPUT_FORMATS = new Set(['json', 'text', 'compact']);
|
|
29
|
+
|
|
30
|
+
const COMMANDS = [
|
|
31
|
+
cmd('system', 'doctor', '环境体检', [], () => null),
|
|
32
|
+
cmd('system', 'schema', '输出机器可读命令 schema', ['-f json'], () => null),
|
|
33
|
+
...scriptCommands,
|
|
34
|
+
...assetCommands,
|
|
35
|
+
...videoCommands,
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const COMMAND_BY_KEY = new Map(COMMANDS.map((item) => [`${item.domain}:${item.name}`, item]));
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv = process.argv.slice(2), env = process.env) {
|
|
41
|
+
const cliEnv = resolveCliEnv(env);
|
|
42
|
+
const args = {
|
|
43
|
+
cliEnv,
|
|
44
|
+
packagedCliEnv: PACKAGE_DEFAULT_CLI_ENV,
|
|
45
|
+
baseUrl: env.ANIME_CLI_BASE_URL || env.ASSET_OUTPUT_BASE_URL || env.AWB_BASE_URL || baseUrlForEnv(cliEnv),
|
|
46
|
+
projectGroupNo: env.ANIME_PROJECT_GROUP_NO || env.ASSET_PROJECT_GROUP_NO || env.PROJECT_GROUP_NO,
|
|
47
|
+
projectId: env.ANIME_PROJECT_ID || env.PROJECT_ID,
|
|
48
|
+
accessKey: env.ANIME_CLI_ACCESS_KEY || env.LINGJING_AWB_ACCESS_KEY || env.AWB_ACCESS_KEY || env.AWB_CODE || env.ANIME_ACCESS_KEY,
|
|
49
|
+
authPath: env.ANIME_CLI_AUTH_PATH || defaultAuthPath(env),
|
|
50
|
+
legacyAuthPath: env.ANIME_CLI_LEGACY_AUTH_PATH || legacyAuthPath(env),
|
|
51
|
+
headers: [],
|
|
52
|
+
format: 'text',
|
|
53
|
+
timeout: 30000,
|
|
54
|
+
retry: 0,
|
|
55
|
+
};
|
|
56
|
+
const tokens = [...argv];
|
|
57
|
+
parseGlobalOptions(args, tokens);
|
|
58
|
+
if (tokens.length === 0) return Object.assign(args, { help: 'root' });
|
|
59
|
+
|
|
60
|
+
const first = tokens.shift();
|
|
61
|
+
if (first === 'doctor' || first === 'schema') {
|
|
62
|
+
Object.assign(args, { domain: 'system', command: first });
|
|
63
|
+
parseCommandOptions(args, tokens);
|
|
64
|
+
return args;
|
|
65
|
+
}
|
|
66
|
+
args.domain = first;
|
|
67
|
+
|
|
68
|
+
if (args.domain === 'system') {
|
|
69
|
+
if (tokens.length === 0 || isHelp(tokens[0])) return Object.assign(args, { help: 'domain' });
|
|
70
|
+
args.command = tokens.shift();
|
|
71
|
+
parseCommandOptions(args, tokens);
|
|
72
|
+
return args;
|
|
73
|
+
}
|
|
74
|
+
if (!DOMAINS.includes(args.domain)) return parseLegacyCommand(args, tokens);
|
|
75
|
+
if (tokens.length === 0 || isHelp(tokens[0])) return Object.assign(args, { help: 'domain' });
|
|
76
|
+
|
|
77
|
+
args.command = tokens.shift();
|
|
78
|
+
parseCommandOptions(args, tokens);
|
|
79
|
+
return args;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseGlobalOptions(args, tokens) {
|
|
83
|
+
while (tokens.length > 0 && tokens[0].startsWith('-')) {
|
|
84
|
+
const token = tokens.shift();
|
|
85
|
+
if (isHelp(token)) {
|
|
86
|
+
args.help = 'root';
|
|
87
|
+
tokens.length = 0;
|
|
88
|
+
} else if (token === '-v' || token === '--version') {
|
|
89
|
+
args.version = true;
|
|
90
|
+
tokens.length = 0;
|
|
91
|
+
} else if (token === '-f' || token === '--format') {
|
|
92
|
+
args.format = parseOutputFormat(takeValue(tokens, token));
|
|
93
|
+
} else if (token === '--json') {
|
|
94
|
+
args.format = 'json';
|
|
95
|
+
} else if (token === '--raw') {
|
|
96
|
+
args.raw = true;
|
|
97
|
+
} else if (token === '--dry-run') {
|
|
98
|
+
args.dryRun = true;
|
|
99
|
+
} else if (token === '--base-url') {
|
|
100
|
+
args.baseUrl = takeValue(tokens, token);
|
|
101
|
+
} else if (token === '-g' || token === '--project-group-no') {
|
|
102
|
+
args.projectGroupNo = takeValue(tokens, token);
|
|
103
|
+
} else if (token === '--project-id') {
|
|
104
|
+
args.projectId = takeValue(tokens, token);
|
|
105
|
+
} else if (token === '--cookie') {
|
|
106
|
+
args.cookie = takeValue(tokens, token);
|
|
107
|
+
} else if (token === '--access-key') {
|
|
108
|
+
args.accessKey = takeValue(tokens, token);
|
|
109
|
+
} else if (token === '--no-access-key') {
|
|
110
|
+
args.noAccessKey = true;
|
|
111
|
+
} else if (token === '--header') {
|
|
112
|
+
args.headers.push(takeValue(tokens, token));
|
|
113
|
+
} else if (token === '--timeout') {
|
|
114
|
+
args.timeout = Number(takeValue(tokens, token)) * 1000;
|
|
115
|
+
} else if (token === '--retry') {
|
|
116
|
+
args.retry = Number(takeValue(tokens, token));
|
|
117
|
+
} else if (token === '--output-file') {
|
|
118
|
+
args.outputFile = takeValue(tokens, token);
|
|
119
|
+
} else {
|
|
120
|
+
throw usageError(`unknown option: ${token}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseCommandOptions(args, tokens) {
|
|
126
|
+
while (tokens.length > 0) {
|
|
127
|
+
const token = tokens.shift();
|
|
128
|
+
if (isHelp(token)) {
|
|
129
|
+
args.help = 'command';
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (token === '-f' || token === '--format') {
|
|
133
|
+
const value = takeValue(tokens, token);
|
|
134
|
+
if (isOutputFormat(value)) args.format = value;
|
|
135
|
+
else args.inputFile = value;
|
|
136
|
+
} else if (token === '--json') args.format = 'json';
|
|
137
|
+
else if (token === '--raw') args.raw = true;
|
|
138
|
+
else if (token === '--dry-run') args.dryRun = true;
|
|
139
|
+
else if (token === '--base-url') args.baseUrl = takeValue(tokens, token);
|
|
140
|
+
else if (token === '-g' || token === '--project-group-no') args.projectGroupNo = takeValue(tokens, token);
|
|
141
|
+
else if (token === '--project-id') args.projectId = takeValue(tokens, token);
|
|
142
|
+
else if (token === '--input-file' || token === '--json-file' || token === '-i') args.inputFile = takeValue(tokens, token);
|
|
143
|
+
else if (token === '--output-file') args.outputFile = takeValue(tokens, token);
|
|
144
|
+
else if (token === '--cookie') args.cookie = takeValue(tokens, token);
|
|
145
|
+
else if (token === '--access-key') args.accessKey = takeValue(tokens, token);
|
|
146
|
+
else if (token === '--no-access-key') args.noAccessKey = true;
|
|
147
|
+
else if (token === '--header') args.headers.push(takeValue(tokens, token));
|
|
148
|
+
else if (token === '--timeout') args.timeout = Number(takeValue(tokens, token)) * 1000;
|
|
149
|
+
else if (token === '--retry') args.retry = Number(takeValue(tokens, token));
|
|
150
|
+
else if (token === '--include-states') args.includeStates = parseOptionalBool(tokens, true);
|
|
151
|
+
else if (token === '--no-include-states') args.includeStates = false;
|
|
152
|
+
else if (token.startsWith('--')) args[kebabToCamel(token.slice(2))] = takeValue(tokens, token);
|
|
153
|
+
else throw usageError(`unexpected argument: ${token}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseLegacyCommand(args, tokens) {
|
|
158
|
+
const resource = args.domain;
|
|
159
|
+
args.domain = 'asset';
|
|
160
|
+
if (resource === 'project') {
|
|
161
|
+
const action = takeLegacyAction(tokens, 'project action');
|
|
162
|
+
args.command = action === 'detail' ? 'get' : action;
|
|
163
|
+
} else if (resource === 'style') {
|
|
164
|
+
const action = takeLegacyAction(tokens, 'style action');
|
|
165
|
+
args.command = action === 'get' ? 'style' : `${action}-style`;
|
|
166
|
+
} else if (['actors', 'props', 'locations'].includes(resource)) {
|
|
167
|
+
parseLegacyAssetResource(args, tokens, resource);
|
|
168
|
+
} else if (resource === 'resources') {
|
|
169
|
+
parseLegacySimpleResource(args, tokens, resource, 'resource');
|
|
170
|
+
} else if (resource === 'resource-tasks') {
|
|
171
|
+
parseLegacyTask(args, tokens, 'resource-task', 'resource-tasks');
|
|
172
|
+
} else if (resource === 'material-tasks') {
|
|
173
|
+
parseLegacyTask(args, tokens, 'material-task', 'material-tasks');
|
|
174
|
+
} else if (resource === 'resource-whitelists') {
|
|
175
|
+
parseLegacySimpleResource(args, tokens, resource, 'resource-whitelist');
|
|
176
|
+
} else {
|
|
177
|
+
throw usageError(`unknown domain: ${resource}`);
|
|
178
|
+
}
|
|
179
|
+
parseCommandOptions(args, tokens);
|
|
180
|
+
return args;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseLegacyAssetResource(args, tokens, resource) {
|
|
184
|
+
const singularByResource = { actors: 'actor', props: 'prop', locations: 'location' };
|
|
185
|
+
const keyByResource = { actors: 'actorKey', props: 'propKey', locations: 'locationKey' };
|
|
186
|
+
const action = takeLegacyAction(tokens, `${resource} action`);
|
|
187
|
+
const singular = singularByResource[resource];
|
|
188
|
+
const keyName = keyByResource[resource];
|
|
189
|
+
if (action === 'list') args.command = resource;
|
|
190
|
+
else if (action === 'get') {
|
|
191
|
+
args.command = singular;
|
|
192
|
+
args[keyName] = takeLegacyAction(tokens, keyName);
|
|
193
|
+
} else if (action === 'upsert') args.command = `upsert-${singular}`;
|
|
194
|
+
else if (action === 'batch') args.command = `batch-${resource}`;
|
|
195
|
+
else if (action === 'delete') {
|
|
196
|
+
args.command = `delete-${singular}`;
|
|
197
|
+
args[keyName] = takeLegacyAction(tokens, keyName);
|
|
198
|
+
} else if (action === 'states') {
|
|
199
|
+
args[keyName] = takeLegacyAction(tokens, keyName);
|
|
200
|
+
const stateAction = takeLegacyAction(tokens, 'state action');
|
|
201
|
+
if (stateAction === 'list') args.command = `${singular}-states`;
|
|
202
|
+
else if (stateAction === 'get') {
|
|
203
|
+
args.command = `${singular}-state`;
|
|
204
|
+
args.stateKey = takeLegacyAction(tokens, 'stateKey');
|
|
205
|
+
} else if (stateAction === 'upsert') args.command = `upsert-${singular}-state`;
|
|
206
|
+
else if (stateAction === 'delete') {
|
|
207
|
+
args.command = `delete-${singular}-state`;
|
|
208
|
+
args.stateKey = takeLegacyAction(tokens, 'stateKey');
|
|
209
|
+
} else throw usageError(`unknown state action: ${stateAction}`);
|
|
210
|
+
} else throw usageError(`unknown ${resource} action: ${action}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseLegacySimpleResource(args, tokens, resource, singular) {
|
|
214
|
+
const action = takeLegacyAction(tokens, `${resource} action`);
|
|
215
|
+
if (action === 'list') args.command = resource;
|
|
216
|
+
else if (action === 'upsert') args.command = `upsert-${singular}`;
|
|
217
|
+
else if (action === 'delete') {
|
|
218
|
+
args.command = `delete-${singular}`;
|
|
219
|
+
args[`${singular.replace(/-/g, '')}Id`] = takeLegacyAction(tokens, `${singular}Id`);
|
|
220
|
+
if (singular === 'resource') args.resourceId = args.resourceId || args.resourceId;
|
|
221
|
+
if (singular === 'resource-whitelist') args.whitelistId = args.resourcewhitelistId;
|
|
222
|
+
} else throw usageError(`unknown ${resource} action: ${action}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function parseLegacyTask(args, tokens, command, name) {
|
|
226
|
+
const action = takeLegacyAction(tokens, `${name} action`);
|
|
227
|
+
if (action === 'list') {
|
|
228
|
+
args.command = 'resource-tasks';
|
|
229
|
+
} else if (action === 'create') {
|
|
230
|
+
args.command = command;
|
|
231
|
+
args.businessType = takeLegacyAction(tokens, 'businessType');
|
|
232
|
+
} else {
|
|
233
|
+
throw usageError(`unknown ${name} action: ${action}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildRequest(args) {
|
|
238
|
+
const command = getCommand(args);
|
|
239
|
+
const headers = buildHeaders(args);
|
|
240
|
+
const route = command.build(args);
|
|
241
|
+
if (route.body !== undefined) headers['content-type'] = headers['content-type'] || 'application/json';
|
|
242
|
+
headers.accept = headers.accept || 'application/json';
|
|
243
|
+
return {
|
|
244
|
+
method: route.method,
|
|
245
|
+
url: buildUrl(args.baseUrl, route.pathParts, route.query),
|
|
246
|
+
headers,
|
|
247
|
+
body: route.body,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function run(args, options = {}) {
|
|
252
|
+
const stdout = options.stdout || process.stdout;
|
|
253
|
+
const stderr = options.stderr || process.stderr;
|
|
254
|
+
if (args.version) return writeAndReturn(stdout, `anime-cli v${VERSION}\n`, 0);
|
|
255
|
+
if (args.help) return writeAndReturn(stdout, helpText(args), 0);
|
|
256
|
+
if (args.domain === 'system' && args.command === 'doctor') return runDoctor(args, stdout);
|
|
257
|
+
if (args.domain === 'system' && args.command === 'schema') return runSchema(args, stdout);
|
|
258
|
+
|
|
259
|
+
let request;
|
|
260
|
+
try {
|
|
261
|
+
request = buildRequest(args);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
stderr.write(`${error.message}\n`);
|
|
264
|
+
return 2;
|
|
265
|
+
}
|
|
266
|
+
if (args.dryRun) {
|
|
267
|
+
writeDryRun(stdout, request, args);
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const fetchImpl = options.fetchImpl || globalThis.fetch;
|
|
272
|
+
if (!fetchImpl) {
|
|
273
|
+
stderr.write('global fetch is unavailable; use Node.js 18 or newer\n');
|
|
274
|
+
return 2;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let response;
|
|
278
|
+
try {
|
|
279
|
+
response = await fetchWithRetry(fetchImpl, request, args);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
stderr.write(`request failed: ${error.message}\n`);
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const content = await decodeResponse(response);
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
stderr.write(`HTTP failed: ${response.status}\n`);
|
|
288
|
+
writeOutput(stderr, content, args);
|
|
289
|
+
return 1;
|
|
290
|
+
}
|
|
291
|
+
if (isApiResult(content)) {
|
|
292
|
+
if (content.code !== SUCCESS_CODE) {
|
|
293
|
+
stderr.write(`API failed: ${content.code} ${content.msg}\n`);
|
|
294
|
+
if (args.raw) writeOutput(stdout, content, args);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
writeOutput(stdout, args.raw ? content : content.data, args);
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
writeOutput(stdout, content, args);
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function runDoctor(args, stdout) {
|
|
305
|
+
const auth = resolveAccessKey(args);
|
|
306
|
+
const data = {
|
|
307
|
+
version: VERSION,
|
|
308
|
+
env: args.cliEnv,
|
|
309
|
+
packagedEnv: args.packagedCliEnv,
|
|
310
|
+
baseUrl: args.baseUrl,
|
|
311
|
+
node: process.version,
|
|
312
|
+
auth: {
|
|
313
|
+
configured: Boolean(auth.value),
|
|
314
|
+
source: auth.source,
|
|
315
|
+
accessKey: mask(auth.value),
|
|
316
|
+
path: auth.path,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
if (args.format === 'json') {
|
|
320
|
+
writeOutput(stdout, data, args);
|
|
321
|
+
} else {
|
|
322
|
+
stdout.write(`anime-cli v${VERSION}\nenv: ${args.cliEnv}\npackagedEnv: ${args.packagedCliEnv}\nbaseUrl: ${args.baseUrl}\nnode: ${process.version}\nauth: ${data.auth.configured ? `configured (${data.auth.source})` : 'not configured'}\n`);
|
|
323
|
+
}
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function runSchema(args, stdout) {
|
|
328
|
+
const schema = {
|
|
329
|
+
version: VERSION,
|
|
330
|
+
domains: Object.fromEntries(DOMAINS.map((domain) => [
|
|
331
|
+
domain,
|
|
332
|
+
COMMANDS.filter((item) => item.domain === domain).map(({ name, summary, options }) => ({ name, summary, options })),
|
|
333
|
+
])),
|
|
334
|
+
};
|
|
335
|
+
writeOutput(stdout, schema, Object.assign({}, args, { format: 'json' }));
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function fetchWithRetry(fetchImpl, request, args) {
|
|
340
|
+
let lastError;
|
|
341
|
+
const attempts = Math.max(0, Number(args.retry) || 0) + 1;
|
|
342
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
343
|
+
try {
|
|
344
|
+
const response = await fetchImpl(request.url, {
|
|
345
|
+
method: request.method,
|
|
346
|
+
headers: request.headers,
|
|
347
|
+
body: request.body,
|
|
348
|
+
signal: abortSignal(args.timeout),
|
|
349
|
+
});
|
|
350
|
+
if (response.status < 500 || i === attempts - 1) return response;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
lastError = error;
|
|
353
|
+
if (i === attempts - 1) throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
throw lastError;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getCommand(args) {
|
|
360
|
+
const command = COMMAND_BY_KEY.get(`${args.domain}:${args.command}`);
|
|
361
|
+
if (!command) throw usageError(`unknown command: ${args.domain} ${args.command || ''}`.trim());
|
|
362
|
+
return command;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function helpText(args = {}) {
|
|
366
|
+
if (!args.domain || args.help === 'root') return rootHelp();
|
|
367
|
+
if (args.help === 'domain') return domainHelp(args.domain);
|
|
368
|
+
return commandHelp(args.domain, args.command);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function rootHelp() {
|
|
372
|
+
return `anime-cli v${VERSION}
|
|
373
|
+
|
|
374
|
+
灵境 Anime Workbench 最终产物命令行工具。默认输出精简文本;需要 JSON 时传 -f json。
|
|
375
|
+
|
|
376
|
+
Usage:
|
|
377
|
+
anime-cli <domain> <command> [options]
|
|
378
|
+
anime-cli <command> [options]
|
|
379
|
+
anime-cli <domain> -h
|
|
380
|
+
anime-cli <domain> <command> -h
|
|
381
|
+
|
|
382
|
+
Quick start:
|
|
383
|
+
anime-cli doctor
|
|
384
|
+
anime-cli schema -f json
|
|
385
|
+
anime-cli script get --project-group-no <projectGroupNo>
|
|
386
|
+
anime-cli asset get --project-group-no <projectGroupNo>
|
|
387
|
+
anime-cli video get --project-group-no <projectGroupNo>
|
|
388
|
+
|
|
389
|
+
Command groups:
|
|
390
|
+
system 系统:环境体检和机器可读命令 schema
|
|
391
|
+
script 剧本最终产物:完整剧本、版本、资产、场景、动作和校验结果
|
|
392
|
+
asset 资产最终产物:角色、道具、场景、资源、任务和加白记录
|
|
393
|
+
video 视频最终产物:集、场、clip 三级产物与视频链接回写
|
|
394
|
+
|
|
395
|
+
More help:
|
|
396
|
+
anime-cli <domain> -h
|
|
397
|
+
anime-cli <domain> <command> -h
|
|
398
|
+
anime-cli schema -f json
|
|
399
|
+
anime-cli doctor -h
|
|
400
|
+
|
|
401
|
+
Global options:
|
|
402
|
+
-f, --format json|text|compact
|
|
403
|
+
输出格式;默认 text,compact 为单行 JSON
|
|
404
|
+
--json 等同于 -f json
|
|
405
|
+
--base-url <url> 服务地址,默认按 ANIME_CLI_ENV 或打包环境选择;当前默认 ${DEFAULT_BASE_URL}
|
|
406
|
+
--access-key <key> Access key;默认读取 ANIME_CLI_ACCESS_KEY 或 lj-awb 本地认证
|
|
407
|
+
--no-access-key 不自动发送 access-key
|
|
408
|
+
--cookie <value> Cookie header
|
|
409
|
+
--header <name:value> 额外 HTTP header,可重复
|
|
410
|
+
--dry-run 只打印请求,不发送
|
|
411
|
+
--retry <n> 网络失败或 5xx 重试次数
|
|
412
|
+
--timeout <seconds> HTTP 超时,默认 30
|
|
413
|
+
--raw 输出完整 ApiResult
|
|
414
|
+
-h, --help 查看帮助
|
|
415
|
+
-v, --version 查看版本
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function domainHelp(domain) {
|
|
420
|
+
if (domain === 'system') {
|
|
421
|
+
return `anime-cli v${VERSION}
|
|
422
|
+
|
|
423
|
+
系统:环境体检和机器可读命令 schema
|
|
424
|
+
|
|
425
|
+
Usage:
|
|
426
|
+
anime-cli system <command> [options]
|
|
427
|
+
anime-cli <command> [options]
|
|
428
|
+
|
|
429
|
+
Commands:
|
|
430
|
+
doctor 环境体检
|
|
431
|
+
schema 输出机器可读命令 schema
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
const title = {
|
|
435
|
+
script: '剧本最终产物:完整剧本、版本、资产、场景、动作和校验结果',
|
|
436
|
+
asset: '资产最终产物:角色、道具、场景、资源、任务和加白记录',
|
|
437
|
+
video: '视频最终产物:集、场、clip 三级产物与视频链接回写',
|
|
438
|
+
}[domain];
|
|
439
|
+
const lines = COMMANDS.filter((item) => item.domain === domain)
|
|
440
|
+
.map((item) => ` ${item.name.padEnd(28)} ${item.summary}`)
|
|
441
|
+
.join('\n');
|
|
442
|
+
return `anime-cli v${VERSION}
|
|
443
|
+
|
|
444
|
+
${title}
|
|
445
|
+
|
|
446
|
+
Usage:
|
|
447
|
+
anime-cli ${domain} <command> [options]
|
|
448
|
+
|
|
449
|
+
Commands:
|
|
450
|
+
${lines}
|
|
451
|
+
|
|
452
|
+
More help:
|
|
453
|
+
anime-cli ${domain} <command> -h
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function commandHelp(domain, commandName) {
|
|
458
|
+
const command = COMMAND_BY_KEY.get(`${domain}:${commandName}`);
|
|
459
|
+
if (!command) return domainHelp(domain);
|
|
460
|
+
return `anime-cli v${VERSION}
|
|
461
|
+
|
|
462
|
+
${command.summary}
|
|
463
|
+
|
|
464
|
+
Usage:
|
|
465
|
+
anime-cli ${domain} ${command.name} [options]
|
|
466
|
+
|
|
467
|
+
Options:
|
|
468
|
+
${command.options.map((line) => ` ${line}`).join('\n') || ' (none)'}
|
|
469
|
+
-f, --format json|text|compact
|
|
470
|
+
--dry-run
|
|
471
|
+
-h, --help
|
|
472
|
+
|
|
473
|
+
Examples:
|
|
474
|
+
${exampleFor(command)}
|
|
475
|
+
`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function exampleFor(command) {
|
|
479
|
+
const d = command.domain;
|
|
480
|
+
const c = command.name;
|
|
481
|
+
if (d === 'script' && c === 'get') return 'anime-cli script get --project-group-no pg_001';
|
|
482
|
+
if (d === 'script' && c === 'replace') return 'anime-cli script replace --project-group-no pg_001 --input-file script.json';
|
|
483
|
+
if (d === 'asset' && c === 'material-task') return 'anime-cli asset material-task --project-group-no pg_001 --business-type image_generate --input-file task.json';
|
|
484
|
+
if (d === 'video' && c === 'update-clip-video-urls') return 'anime-cli video update-clip-video-urls --project-group-no pg_001 --episode-id ep_001 --scene-id scn_001 --clip-id clip_001 --input-file video-urls.json';
|
|
485
|
+
return `anime-cli ${d} ${c} ${command.options.join(' ')}`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function writeOutput(stream, value, args) {
|
|
489
|
+
const text = formatOutput(value, args.format);
|
|
490
|
+
if (args.outputFile) fs.writeFileSync(args.outputFile, text);
|
|
491
|
+
else stream.write(text);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function formatOutput(value, format = 'text') {
|
|
495
|
+
if (format === 'json') return `${JSON.stringify(value, null, 2)}\n`;
|
|
496
|
+
if (format === 'compact') return `${JSON.stringify(value)}\n`;
|
|
497
|
+
return textSummary(value);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function textSummary(value) {
|
|
501
|
+
if (value === undefined || value === null) return 'OK\n';
|
|
502
|
+
if (Array.isArray(value)) return `${value.length} items\n`;
|
|
503
|
+
if (typeof value === 'object') {
|
|
504
|
+
if ('id' in value) return `id: ${value.id}\n`;
|
|
505
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
506
|
+
}
|
|
507
|
+
return `${String(value)}\n`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function writeDryRun(stream, request, args) {
|
|
511
|
+
const payload = {
|
|
512
|
+
method: request.method,
|
|
513
|
+
url: request.url,
|
|
514
|
+
headers: maskSensitiveHeaders(request.headers),
|
|
515
|
+
body: request.body ? JSON.parse(request.body) : undefined,
|
|
516
|
+
};
|
|
517
|
+
if (args.format === 'json' || args.format === 'compact') stream.write(formatOutput(payload, args.format));
|
|
518
|
+
else {
|
|
519
|
+
stream.write(`${request.method} ${request.url}\n`);
|
|
520
|
+
if (request.body) stream.write(`${request.body}\n`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function maskSensitiveHeaders(headers) {
|
|
525
|
+
return Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => {
|
|
526
|
+
if (['access-key', 'x-access-key', 'authorization', 'cookie'].includes(key.toLowerCase())) {
|
|
527
|
+
return [key, mask(String(value))];
|
|
528
|
+
}
|
|
529
|
+
return [key, value];
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function buildHeaders(args) {
|
|
534
|
+
const headers = {};
|
|
535
|
+
if (args.cookie) headers.cookie = args.cookie;
|
|
536
|
+
if (args.userId) headers.userid = args.userId;
|
|
537
|
+
const accessKey = resolveAccessKey(args).value;
|
|
538
|
+
if (accessKey) headers['X-Access-Key'] = accessKey;
|
|
539
|
+
for (const value of args.headers || []) {
|
|
540
|
+
const index = value.indexOf(':');
|
|
541
|
+
if (index < 1) throw usageError(`invalid --header value: ${value}`);
|
|
542
|
+
headers[value.slice(0, index).trim().toLowerCase()] = value.slice(index + 1).trim();
|
|
543
|
+
}
|
|
544
|
+
return headers;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function resolveAccessKey(args) {
|
|
548
|
+
if (args.noAccessKey) return { value: null, source: 'disabled' };
|
|
549
|
+
if (args.accessKey) return { value: args.accessKey, source: 'option/env' };
|
|
550
|
+
for (const authPath of [args.authPath, args.legacyAuthPath]) {
|
|
551
|
+
const value = readAccessKeyFromFile(authPath);
|
|
552
|
+
if (value) return { value, source: 'saved/auth', path: authPath };
|
|
553
|
+
}
|
|
554
|
+
return { value: null, source: 'none' };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function readAccessKeyFromFile(authPath) {
|
|
558
|
+
if (!authPath || !fs.existsSync(authPath)) return null;
|
|
559
|
+
try {
|
|
560
|
+
const auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
561
|
+
return auth.accessKey || auth.awbCode || auth.awb_code || auth.access_key || null;
|
|
562
|
+
} catch {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function defaultAuthPath(env = process.env) {
|
|
568
|
+
return path.join(homeDir(env), '.lingjingai', 'awb', 'auth.json');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function legacyAuthPath(env = process.env) {
|
|
572
|
+
return path.join(homeDir(env), '.opencli', 'awb-auth.json');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function homeDir(env = process.env) {
|
|
576
|
+
return env.HOME || os.homedir();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function mask(value) {
|
|
580
|
+
if (!value) return null;
|
|
581
|
+
if (value.length <= 8) return '****';
|
|
582
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function decodeResponse(response) {
|
|
586
|
+
const text = await response.text();
|
|
587
|
+
if (!text) return null;
|
|
588
|
+
const contentType = getHeader(response.headers, 'content-type');
|
|
589
|
+
if (contentType.includes('json') || looksLikeJson(text)) return JSON.parse(text);
|
|
590
|
+
return text;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function getHeader(headers, name) {
|
|
594
|
+
if (!headers) return '';
|
|
595
|
+
if (typeof headers.get === 'function') return headers.get(name) || '';
|
|
596
|
+
if (headers instanceof Map) return headers.get(name) || '';
|
|
597
|
+
return headers[name] || headers[name.toLowerCase()] || '';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function buildUrl(baseUrl, pathParts, query = {}) {
|
|
601
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
602
|
+
const url = new URL(`${normalizedBaseUrl.replace(/\/+$/, '')}/${pathParts.map((part) => encodeURIComponent(String(part))).join('/')}`);
|
|
603
|
+
for (const [key, value] of Object.entries(query || {})) {
|
|
604
|
+
if (value !== undefined && value !== null) url.searchParams.set(key, value);
|
|
605
|
+
}
|
|
606
|
+
return url.toString();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function normalizeBaseUrl(baseUrl) {
|
|
610
|
+
const url = new URL(baseUrl);
|
|
611
|
+
const path = url.pathname.replace(/\/+$/, '');
|
|
612
|
+
if (path === '' || path === '/') {
|
|
613
|
+
url.pathname = WORKBENCH_CONTEXT_PATH;
|
|
614
|
+
} else if (path === '/api/anime') {
|
|
615
|
+
url.pathname = WORKBENCH_CONTEXT_PATH;
|
|
616
|
+
} else if (!path.endsWith(WORKBENCH_CONTEXT_PATH)) {
|
|
617
|
+
url.pathname = `${path}${WORKBENCH_CONTEXT_PATH}`;
|
|
618
|
+
} else {
|
|
619
|
+
url.pathname = path;
|
|
620
|
+
}
|
|
621
|
+
return url.toString().replace(/\/+$/, '');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function resolveCliEnv(env = process.env) {
|
|
625
|
+
return normalizeCliEnv(env.ANIME_CLI_ENV || PACKAGE_DEFAULT_CLI_ENV);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function normalizeCliEnv(value) {
|
|
629
|
+
const cliEnv = String(value || DEFAULT_PACK_ENV).trim().toLowerCase();
|
|
630
|
+
return Object.prototype.hasOwnProperty.call(BASE_URL_BY_ENV, cliEnv) ? cliEnv : DEFAULT_PACK_ENV;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function assertCliEnv(value) {
|
|
634
|
+
const cliEnv = String(value || DEFAULT_PACK_ENV).trim().toLowerCase();
|
|
635
|
+
if (!Object.prototype.hasOwnProperty.call(BASE_URL_BY_ENV, cliEnv)) {
|
|
636
|
+
throw new Error(`unsupported ANIME_CLI_PACK_ENV: ${value}. expected one of ${Object.keys(BASE_URL_BY_ENV).join(', ')}`);
|
|
637
|
+
}
|
|
638
|
+
return cliEnv;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function baseUrlForEnv(cliEnv) {
|
|
642
|
+
return BASE_URL_BY_ENV[normalizeCliEnv(cliEnv)];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function packageNameForEnv(cliEnv) {
|
|
646
|
+
return PACKAGE_NAME_BY_ENV[normalizeCliEnv(cliEnv)];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function preparePackageEnv(value = DEFAULT_PACK_ENV) {
|
|
650
|
+
const cliEnv = assertCliEnv(value);
|
|
651
|
+
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
652
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
653
|
+
packageJson.name = packageNameForEnv(cliEnv);
|
|
654
|
+
packageJson.config = packageJson.config || {};
|
|
655
|
+
packageJson.config.animeCliEnv = cliEnv;
|
|
656
|
+
packageJson.config.animeCliBaseUrls = Object.assign(
|
|
657
|
+
{},
|
|
658
|
+
DEFAULT_BASE_URL_BY_ENV,
|
|
659
|
+
packageJson.config.animeCliBaseUrls || {},
|
|
660
|
+
);
|
|
661
|
+
packageJson.config.animeCliPackageNames = Object.assign(
|
|
662
|
+
{},
|
|
663
|
+
DEFAULT_PACKAGE_NAME_BY_ENV,
|
|
664
|
+
packageJson.config.animeCliPackageNames || {},
|
|
665
|
+
);
|
|
666
|
+
fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function packForEnv(value, packDestination) {
|
|
670
|
+
const cliEnv = assertCliEnv(value);
|
|
671
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
672
|
+
const { spawnSync } = require('node:child_process');
|
|
673
|
+
const npmArgs = ['pack'];
|
|
674
|
+
if (packDestination) {
|
|
675
|
+
fs.mkdirSync(packDestination, { recursive: true });
|
|
676
|
+
npmArgs.push('--pack-destination', packDestination);
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
preparePackageEnv(cliEnv);
|
|
680
|
+
const result = spawnSync(npmCommand, npmArgs, {
|
|
681
|
+
stdio: 'inherit',
|
|
682
|
+
env: Object.assign({}, process.env, { ANIME_CLI_PACK_ENV: cliEnv }),
|
|
683
|
+
});
|
|
684
|
+
if (result.error) throw result.error;
|
|
685
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
686
|
+
} finally {
|
|
687
|
+
preparePackageEnv(DEFAULT_PACK_ENV);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function cmd(domain, name, summary, options, build) {
|
|
692
|
+
return { domain, name, summary, options, build };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function parseOptionalBool(tokens, defaultValue) {
|
|
696
|
+
if (tokens.length === 0 || tokens[0].startsWith('-')) return defaultValue;
|
|
697
|
+
const value = tokens.shift();
|
|
698
|
+
return value !== 'false';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function abortSignal(timeout) {
|
|
702
|
+
if (!timeout || !Number.isFinite(timeout)) return undefined;
|
|
703
|
+
return AbortSignal.timeout(timeout);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function takeValue(tokens, option) {
|
|
707
|
+
if (tokens.length === 0 || tokens[0].startsWith('-')) throw usageError(`missing value for ${option}`);
|
|
708
|
+
return tokens.shift();
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function takeLegacyAction(tokens, name) {
|
|
712
|
+
if (tokens.length === 0 || isHelp(tokens[0])) throw usageError(`missing ${name}`);
|
|
713
|
+
return tokens.shift();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function parseOutputFormat(value) {
|
|
717
|
+
if (!isOutputFormat(value)) throw usageError(`unsupported format: ${value}. expected json, text or compact`);
|
|
718
|
+
return value;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function isOutputFormat(value) {
|
|
722
|
+
return OUTPUT_FORMATS.has(value);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function isHelp(value) {
|
|
726
|
+
return value === '-h' || value === '--help';
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function camelToKebab(value) {
|
|
730
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function kebabToCamel(value) {
|
|
734
|
+
return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function looksLikeJson(text) {
|
|
738
|
+
const trimmed = text.trimStart();
|
|
739
|
+
return trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function isApiResult(value) {
|
|
743
|
+
return value && typeof value === 'object' && 'code' in value && 'msg' in value && 'data' in value;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function usageError(message) {
|
|
747
|
+
const error = new Error(message);
|
|
748
|
+
error.isUsageError = true;
|
|
749
|
+
return error;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function writeAndReturn(stream, value, exitCode) {
|
|
753
|
+
stream.write(value);
|
|
754
|
+
return exitCode;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function main(argv) {
|
|
758
|
+
let args;
|
|
759
|
+
try {
|
|
760
|
+
args = parseArgs(argv);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
process.stderr.write(`${error.message}\n\n${rootHelp()}`);
|
|
763
|
+
return Promise.resolve(2);
|
|
764
|
+
}
|
|
765
|
+
return run(args);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
module.exports = {
|
|
769
|
+
DEFAULT_BASE_URL_BY_ENV,
|
|
770
|
+
DEFAULT_PACKAGE_NAME_BY_ENV,
|
|
771
|
+
DEFAULT_BASE_URL,
|
|
772
|
+
DEFAULT_PACK_ENV,
|
|
773
|
+
PACKAGE_DEFAULT_CLI_ENV,
|
|
774
|
+
VERSION,
|
|
775
|
+
COMMANDS,
|
|
776
|
+
parseArgs,
|
|
777
|
+
buildRequest,
|
|
778
|
+
normalizeBaseUrl,
|
|
779
|
+
packageNameForEnv,
|
|
780
|
+
preparePackageEnv,
|
|
781
|
+
packForEnv,
|
|
782
|
+
run,
|
|
783
|
+
main,
|
|
784
|
+
helpText,
|
|
785
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
|
|
3
|
+
function cmd(domain, name, summary, options, build) {
|
|
4
|
+
return { domain, name, summary, options, build };
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function get(pathParts, query) {
|
|
8
|
+
return { method: 'GET', pathParts, query };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function post(pathParts, args, fields, options = {}) {
|
|
12
|
+
return { method: 'POST', pathParts, body: buildRequestBody(args, fields, options) };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function put(pathParts, args) {
|
|
16
|
+
return { method: 'PUT', pathParts, body: readJsonBody(args) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function scriptRoot(args) {
|
|
20
|
+
return ['script-output', 'project-groups', req(args, 'projectGroupNo')];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assetRoot(args) {
|
|
24
|
+
return ['asset', 'project-groups', req(args, 'projectGroupNo')];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function videoRoot(args) {
|
|
28
|
+
return ['video-output', 'project-groups', req(args, 'projectGroupNo')];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildRequestBody(args, fields, options = {}) {
|
|
32
|
+
if (!fields) return args && args.inputFile ? readJsonBody(args) : undefined;
|
|
33
|
+
const cliBody = bodyFromArgs(args, fields);
|
|
34
|
+
const hasCliBody = Object.keys(cliBody).length > 0;
|
|
35
|
+
if (!args.inputFile && !hasCliBody) return undefined;
|
|
36
|
+
const fileBody = args.inputFile ? readJsonValue(args) : undefined;
|
|
37
|
+
const merged = mergeBody(fileBody, cliBody, hasCliBody);
|
|
38
|
+
const body = options.array && !Array.isArray(merged) ? [merged] : merged;
|
|
39
|
+
return JSON.stringify(body);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mergeBody(fileBody, cliBody, hasCliBody) {
|
|
43
|
+
if (fileBody === undefined) return cliBody;
|
|
44
|
+
if (!hasCliBody) return fileBody;
|
|
45
|
+
if (Array.isArray(fileBody)) {
|
|
46
|
+
return fileBody.map((item) => {
|
|
47
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
48
|
+
throw usageError('cannot merge body options into non-object array items');
|
|
49
|
+
}
|
|
50
|
+
return Object.assign({}, item, cliBody);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!fileBody || typeof fileBody !== 'object') {
|
|
54
|
+
throw usageError('cannot merge body options into non-object input file');
|
|
55
|
+
}
|
|
56
|
+
return Object.assign({}, fileBody, cliBody);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function bodyFromArgs(args, fields) {
|
|
60
|
+
return Object.fromEntries(fields
|
|
61
|
+
.filter((field) => args[field.name] !== undefined)
|
|
62
|
+
.map((field) => [field.name, parseBodyFieldValue(args[field.name], field)]));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseBodyFieldValue(value, field) {
|
|
66
|
+
if (field.type === 'string') return value;
|
|
67
|
+
if (field.type === 'number') return parseNumber(value, field.name);
|
|
68
|
+
if (field.type === 'boolean') return parseBoolean(value, field.name);
|
|
69
|
+
if (field.type === 'json') return parseJsonOption(value, field.name);
|
|
70
|
+
if (field.type === 'numberList') return parseList(value, field.name, (item) => parseNumber(item, field.name));
|
|
71
|
+
if (field.type === 'stringList') return parseList(value, field.name, String);
|
|
72
|
+
throw usageError(`unsupported body field type: ${field.type}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseNumber(value, name) {
|
|
76
|
+
const number = Number(value);
|
|
77
|
+
if (!Number.isFinite(number)) throw usageError(`--${camelToKebab(name)} must be a number`);
|
|
78
|
+
return number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseBoolean(value, name) {
|
|
82
|
+
if (value === true || value === false) return value;
|
|
83
|
+
if (value === 'true' || value === '1') return true;
|
|
84
|
+
if (value === 'false' || value === '0') return false;
|
|
85
|
+
throw usageError(`--${camelToKebab(name)} must be true or false`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseJsonOption(value, name) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(value);
|
|
91
|
+
} catch {
|
|
92
|
+
throw usageError(`--${camelToKebab(name)} must be valid JSON`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseList(value, name, mapItem) {
|
|
97
|
+
const items = String(value).trimStart().startsWith('[')
|
|
98
|
+
? parseJsonOption(value, name)
|
|
99
|
+
: String(value).split(',').map((item) => item.trim()).filter(Boolean);
|
|
100
|
+
if (!Array.isArray(items)) throw usageError(`--${camelToKebab(name)} must be a comma-separated list or JSON array`);
|
|
101
|
+
return items.map(mapItem);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readJsonBody(args) {
|
|
105
|
+
return JSON.stringify(readJsonValue(args));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readJsonValue(args) {
|
|
109
|
+
if (!args.inputFile) throw usageError('missing --input-file <file|->');
|
|
110
|
+
const raw = args.inputFile === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(args.inputFile, 'utf8');
|
|
111
|
+
return JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function bodyCommandOptions(fields, pathOptions = []) {
|
|
115
|
+
return ['--project-group-no <no>', ...pathOptions, '--input-file <file|->', ...bodyOptions(fields)];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function bodyOptions(fields) {
|
|
119
|
+
return fields.map((field) => `--${camelToKebab(field.name)} <${bodyOptionValueLabel(field.type)}>`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function bodyOptionValueLabel(type) {
|
|
123
|
+
if (type === 'boolean') return 'true|false';
|
|
124
|
+
if (type === 'json') return 'json';
|
|
125
|
+
if (type === 'numberList' || type === 'stringList') return 'csv|json';
|
|
126
|
+
return 'value';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function bodyFields(fields) {
|
|
130
|
+
return fields.map((field) => {
|
|
131
|
+
if (Array.isArray(field)) return { name: field[0], type: field[1] };
|
|
132
|
+
return { name: field, type: inferBodyFieldType(field) };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function inferBodyFieldType(name) {
|
|
137
|
+
if (['id', 'actorId', 'locationId', 'propId', 'stateId', 'assetId', 'parentResourceId', 'assetResourceTaskId'].includes(name)) {
|
|
138
|
+
return 'number';
|
|
139
|
+
}
|
|
140
|
+
return 'string';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function pick(args, fields) {
|
|
144
|
+
return Object.fromEntries(fields.filter((field) => args[field] !== undefined).map((field) => [field, args[field]]));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function req(args, field) {
|
|
148
|
+
if (args[field] === undefined || args[field] === '') throw usageError(`missing --${camelToKebab(field)}`);
|
|
149
|
+
return args[field];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function businessType(args) {
|
|
153
|
+
const value = req(args, 'businessType');
|
|
154
|
+
if (!['image_edit', 'image_generate'].includes(value)) throw usageError('businessType must be image_edit or image_generate');
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function boolText(value, defaultValue) {
|
|
159
|
+
return String(value === undefined ? defaultValue : value);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function camelToKebab(value) {
|
|
163
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function usageError(message) {
|
|
167
|
+
const error = new Error(message);
|
|
168
|
+
error.isUsageError = true;
|
|
169
|
+
return error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
assetRoot,
|
|
174
|
+
bodyCommandOptions,
|
|
175
|
+
bodyFields,
|
|
176
|
+
boolText,
|
|
177
|
+
businessType,
|
|
178
|
+
camelToKebab,
|
|
179
|
+
cmd,
|
|
180
|
+
get,
|
|
181
|
+
pick,
|
|
182
|
+
post,
|
|
183
|
+
put,
|
|
184
|
+
req,
|
|
185
|
+
scriptRoot,
|
|
186
|
+
videoRoot,
|
|
187
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const {
|
|
2
|
+
assetRoot,
|
|
3
|
+
bodyCommandOptions,
|
|
4
|
+
bodyFields,
|
|
5
|
+
boolText,
|
|
6
|
+
businessType,
|
|
7
|
+
camelToKebab,
|
|
8
|
+
cmd,
|
|
9
|
+
get,
|
|
10
|
+
pick,
|
|
11
|
+
post,
|
|
12
|
+
req,
|
|
13
|
+
} = require('../command-helpers');
|
|
14
|
+
|
|
15
|
+
const ASSET_FILTER_FIELDS = ['assetType', 'assetId', 'stateId', 'resourceType', 'resourceSlot'];
|
|
16
|
+
const WHITELIST_FILTER_FIELDS = ['assetType', 'assetId', 'stateId', 'resourceId', 'platform'];
|
|
17
|
+
const PROJECT_STYLE_BODY_FIELDS = bodyFields([
|
|
18
|
+
'id',
|
|
19
|
+
'projectName',
|
|
20
|
+
'projectDisplay',
|
|
21
|
+
'worldviewType',
|
|
22
|
+
'assetsWorldviewType',
|
|
23
|
+
'worldviewSubtype',
|
|
24
|
+
'era',
|
|
25
|
+
'visualMode',
|
|
26
|
+
'appearanceRegion',
|
|
27
|
+
'appearanceSubtype',
|
|
28
|
+
['appearanceRegionTraits', 'json'],
|
|
29
|
+
'nativeLanguage',
|
|
30
|
+
['sceneStyle', 'json'],
|
|
31
|
+
['characterStyle', 'json'],
|
|
32
|
+
['propStyle', 'json'],
|
|
33
|
+
'antiContamination',
|
|
34
|
+
['lab', 'json'],
|
|
35
|
+
]);
|
|
36
|
+
const ACTOR_BODY_FIELDS = bodyFields([
|
|
37
|
+
'id',
|
|
38
|
+
'actorKey',
|
|
39
|
+
'actorName',
|
|
40
|
+
'roleType',
|
|
41
|
+
['lab', 'json'],
|
|
42
|
+
'name',
|
|
43
|
+
'voiceText',
|
|
44
|
+
'voiceDesc',
|
|
45
|
+
'voiceUrl',
|
|
46
|
+
'voiceStatus',
|
|
47
|
+
['voiceCandidates', 'json'],
|
|
48
|
+
'selectedVoiceCandidateId',
|
|
49
|
+
'voiceBackend',
|
|
50
|
+
'voiceReferenceImageUrl',
|
|
51
|
+
['states', 'json'],
|
|
52
|
+
]);
|
|
53
|
+
const ACTOR_STATE_BODY_FIELDS = bodyFields([
|
|
54
|
+
'id',
|
|
55
|
+
'actorId',
|
|
56
|
+
'stateKey',
|
|
57
|
+
'stateName',
|
|
58
|
+
'gender',
|
|
59
|
+
'age',
|
|
60
|
+
'assetsWorldviewType',
|
|
61
|
+
'description',
|
|
62
|
+
['episodes', 'json'],
|
|
63
|
+
'genPrompt',
|
|
64
|
+
'promptHead',
|
|
65
|
+
'promptBody',
|
|
66
|
+
['matchedHeadAsset', 'json'],
|
|
67
|
+
['matchedBodyAsset', 'json'],
|
|
68
|
+
['lab', 'json'],
|
|
69
|
+
'roleType',
|
|
70
|
+
'subjectId',
|
|
71
|
+
'threeViewId',
|
|
72
|
+
'status',
|
|
73
|
+
'faceView',
|
|
74
|
+
'faceViewUrl',
|
|
75
|
+
'sideView',
|
|
76
|
+
'sideViewUrl',
|
|
77
|
+
'backView',
|
|
78
|
+
'backViewUrl',
|
|
79
|
+
'threeView',
|
|
80
|
+
'threeViewUrl',
|
|
81
|
+
'headCloseup',
|
|
82
|
+
'headCloseupUrl',
|
|
83
|
+
'frontFullBodyPrompt',
|
|
84
|
+
'threeViewPrompt',
|
|
85
|
+
['resources', 'json'],
|
|
86
|
+
]);
|
|
87
|
+
const PROP_BODY_FIELDS = bodyFields([
|
|
88
|
+
'id',
|
|
89
|
+
'propKey',
|
|
90
|
+
'propName',
|
|
91
|
+
'groupName',
|
|
92
|
+
['groupDefault', 'boolean'],
|
|
93
|
+
['imagePrompts', 'json'],
|
|
94
|
+
['episodes', 'json'],
|
|
95
|
+
['lab', 'json'],
|
|
96
|
+
['states', 'json'],
|
|
97
|
+
]);
|
|
98
|
+
const LOCATION_BODY_FIELDS = bodyFields([
|
|
99
|
+
'id',
|
|
100
|
+
'locationKey',
|
|
101
|
+
'locationName',
|
|
102
|
+
'groupSheetUrl',
|
|
103
|
+
'groupName',
|
|
104
|
+
['groupDefault', 'boolean'],
|
|
105
|
+
['imagePrompts', 'json'],
|
|
106
|
+
['episodes', 'json'],
|
|
107
|
+
['lab', 'json'],
|
|
108
|
+
['states', 'json'],
|
|
109
|
+
]);
|
|
110
|
+
const SIMPLE_STATE_BODY_FIELDS = bodyFields([
|
|
111
|
+
'id',
|
|
112
|
+
'stateKey',
|
|
113
|
+
'stateName',
|
|
114
|
+
'description',
|
|
115
|
+
'genPrompt',
|
|
116
|
+
['lab', 'json'],
|
|
117
|
+
'status',
|
|
118
|
+
'main',
|
|
119
|
+
'mainUrl',
|
|
120
|
+
'auxiliary',
|
|
121
|
+
'auxiliaryUrl',
|
|
122
|
+
['resources', 'json'],
|
|
123
|
+
]);
|
|
124
|
+
const RESOURCE_BODY_FIELDS = bodyFields([
|
|
125
|
+
'id',
|
|
126
|
+
'assetType',
|
|
127
|
+
'assetId',
|
|
128
|
+
'stateId',
|
|
129
|
+
'resourceType',
|
|
130
|
+
'resourceSlot',
|
|
131
|
+
'sourceType',
|
|
132
|
+
'parentResourceId',
|
|
133
|
+
'prompt',
|
|
134
|
+
['promptParams', 'json'],
|
|
135
|
+
'assetResourceTaskId',
|
|
136
|
+
'status',
|
|
137
|
+
'url',
|
|
138
|
+
'errorMsg',
|
|
139
|
+
['lab', 'json'],
|
|
140
|
+
]);
|
|
141
|
+
const RESOURCE_TASK_BODY_FIELDS = bodyFields([
|
|
142
|
+
'actorId',
|
|
143
|
+
'locationId',
|
|
144
|
+
'propId',
|
|
145
|
+
'stateId',
|
|
146
|
+
'resourceType',
|
|
147
|
+
'resourceSlot',
|
|
148
|
+
'sourceType',
|
|
149
|
+
'parentResourceId',
|
|
150
|
+
'modelGroupCode',
|
|
151
|
+
'taskPrompt',
|
|
152
|
+
['promptParams', 'json'],
|
|
153
|
+
]);
|
|
154
|
+
const WHITELIST_BODY_FIELDS = bodyFields([
|
|
155
|
+
['actorIds', 'numberList'],
|
|
156
|
+
['locationIds', 'numberList'],
|
|
157
|
+
['propIds', 'numberList'],
|
|
158
|
+
['resourceIds', 'numberList'],
|
|
159
|
+
['resourceSlotList', 'stringList'],
|
|
160
|
+
'whitelistType',
|
|
161
|
+
'platform',
|
|
162
|
+
'remark',
|
|
163
|
+
['lab', 'json'],
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const commands = [
|
|
167
|
+
cmd('asset', 'get', '查询项目组全部资产产物', ['--project-group-no <no>'], (a) => get(assetRoot(a))),
|
|
168
|
+
cmd('asset', 'output', '查询资产详情视图', ['--project-group-no <no>'], (a) => get([...assetRoot(a), 'output'])),
|
|
169
|
+
cmd('asset', 'style', '查询项目资产风格配置', ['--project-group-no <no>'], (a) => get([...assetRoot(a), 'style'])),
|
|
170
|
+
cmd('asset', 'upsert-style', '新增或更新项目资产风格配置', bodyCommandOptions(PROJECT_STYLE_BODY_FIELDS), (a) => post([...assetRoot(a), 'style'], a, PROJECT_STYLE_BODY_FIELDS)),
|
|
171
|
+
cmd('asset', 'delete-style', '逻辑删除项目资产风格配置', ['--project-group-no <no>'], (a) => post([...assetRoot(a), 'style', 'delete'])),
|
|
172
|
+
cmd('asset', 'actors', '查询项目组角色列表', ['--project-group-no <no>', '--include-states <true|false>'], (a) => get([...assetRoot(a), 'actors'], { includeStates: boolText(a.includeStates, true) })),
|
|
173
|
+
cmd('asset', 'actor', '查询单个角色', ['--project-group-no <no>', '--actor-key <key>', '--include-states <true|false>'], (a) => get([...assetRoot(a), 'actors', req(a, 'actorKey')], { includeStates: boolText(a.includeStates, true) })),
|
|
174
|
+
cmd('asset', 'upsert-actor', '新增或更新角色', bodyCommandOptions(ACTOR_BODY_FIELDS), (a) => post([...assetRoot(a), 'actors'], a, ACTOR_BODY_FIELDS)),
|
|
175
|
+
cmd('asset', 'batch-actors', '批量新增或更新角色', bodyCommandOptions(ACTOR_BODY_FIELDS), (a) => post([...assetRoot(a), 'actors', 'batch'], a, ACTOR_BODY_FIELDS, { array: true })),
|
|
176
|
+
cmd('asset', 'delete-actor', '逻辑删除角色', ['--project-group-no <no>', '--actor-key <key>'], (a) => post([...assetRoot(a), 'actors', req(a, 'actorKey'), 'delete'])),
|
|
177
|
+
cmd('asset', 'actor-states', '查询角色状态列表', ['--project-group-no <no>', '--actor-key <key>'], (a) => get([...assetRoot(a), 'actors', req(a, 'actorKey'), 'states'])),
|
|
178
|
+
cmd('asset', 'actor-state', '查询单个角色状态', ['--project-group-no <no>', '--actor-key <key>', '--state-key <key>'], (a) => get([...assetRoot(a), 'actors', req(a, 'actorKey'), 'states', req(a, 'stateKey')])),
|
|
179
|
+
cmd('asset', 'upsert-actor-state', '新增或更新角色状态', bodyCommandOptions(ACTOR_STATE_BODY_FIELDS, ['--actor-key <key>']), (a) => post([...assetRoot(a), 'actors', req(a, 'actorKey'), 'states'], a, ACTOR_STATE_BODY_FIELDS)),
|
|
180
|
+
cmd('asset', 'delete-actor-state', '逻辑删除角色状态', ['--project-group-no <no>', '--actor-key <key>', '--state-key <key>'], (a) => post([...assetRoot(a), 'actors', req(a, 'actorKey'), 'states', req(a, 'stateKey'), 'delete'])),
|
|
181
|
+
...assetTypeCommands('props', 'prop', 'propKey', '道具'),
|
|
182
|
+
...assetTypeCommands('locations', 'location', 'locationKey', '场景'),
|
|
183
|
+
cmd('asset', 'resources', '查询资产资源列表', assetFilterOptions(), (a) => get([...assetRoot(a), 'resources'], pick(a, ASSET_FILTER_FIELDS))),
|
|
184
|
+
cmd('asset', 'upsert-resource', '新增或更新资产资源', bodyCommandOptions(RESOURCE_BODY_FIELDS), (a) => post([...assetRoot(a), 'resources'], a, RESOURCE_BODY_FIELDS)),
|
|
185
|
+
cmd('asset', 'delete-resource', '逻辑删除资产资源', ['--project-group-no <no>', '--resource-id <id>'], (a) => post([...assetRoot(a), 'resources', req(a, 'resourceId'), 'delete'])),
|
|
186
|
+
cmd('asset', 'resource-tasks', '查询资产资源任务列表', assetFilterOptions(), (a) => get([...assetRoot(a), 'resource-tasks'], pick(a, ASSET_FILTER_FIELDS))),
|
|
187
|
+
cmd('asset', 'resource-task', '创建资产图片异步任务', bodyCommandOptions(RESOURCE_TASK_BODY_FIELDS, ['--business-type <image_edit|image_generate>']), (a) => post([...assetRoot(a), 'resource-tasks', businessType(a)], a, RESOURCE_TASK_BODY_FIELDS)),
|
|
188
|
+
cmd('asset', 'material-task', '创建资产图片异步任务', bodyCommandOptions(RESOURCE_TASK_BODY_FIELDS, ['--business-type <image_edit|image_generate>']), (a) => post([...assetRoot(a), 'material-tasks', businessType(a)], a, RESOURCE_TASK_BODY_FIELDS)),
|
|
189
|
+
cmd('asset', 'resource-whitelists', '查询资产资源加白记录', whitelistFilterOptions(), (a) => get([...assetRoot(a), 'resource-whitelists'], pick(a, WHITELIST_FILTER_FIELDS))),
|
|
190
|
+
cmd('asset', 'upsert-resource-whitelist', '新增或更新资产资源加白记录', bodyCommandOptions(WHITELIST_BODY_FIELDS), (a) => post([...assetRoot(a), 'resource-whitelists'], a, WHITELIST_BODY_FIELDS)),
|
|
191
|
+
cmd('asset', 'delete-resource-whitelist', '逻辑删除资产资源加白记录', ['--project-group-no <no>', '--whitelist-id <id>'], (a) => post([...assetRoot(a), 'resource-whitelists', req(a, 'whitelistId'), 'delete'])),
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
function assetTypeCommands(plural, singular, keyName, label) {
|
|
195
|
+
const fields = singular === 'location' ? LOCATION_BODY_FIELDS : PROP_BODY_FIELDS;
|
|
196
|
+
const stateFields = SIMPLE_STATE_BODY_FIELDS;
|
|
197
|
+
return [
|
|
198
|
+
cmd('asset', plural, `查询项目组${label}列表`, ['--project-group-no <no>', '--include-states <true|false>'], (a) => get([...assetRoot(a), plural], { includeStates: boolText(a.includeStates, true) })),
|
|
199
|
+
cmd('asset', singular, `查询单个${label}`, ['--project-group-no <no>', `--${camelToKebab(keyName)} <key>`, '--include-states <true|false>'], (a) => get([...assetRoot(a), plural, req(a, keyName)], { includeStates: boolText(a.includeStates, true) })),
|
|
200
|
+
cmd('asset', `upsert-${singular}`, `新增或更新${label}`, bodyCommandOptions(fields), (a) => post([...assetRoot(a), plural], a, fields)),
|
|
201
|
+
cmd('asset', `batch-${plural}`, `批量新增或更新${label}`, bodyCommandOptions(fields), (a) => post([...assetRoot(a), plural, 'batch'], a, fields, { array: true })),
|
|
202
|
+
cmd('asset', `delete-${singular}`, `逻辑删除${label}`, ['--project-group-no <no>', `--${camelToKebab(keyName)} <key>`], (a) => post([...assetRoot(a), plural, req(a, keyName), 'delete'])),
|
|
203
|
+
cmd('asset', `${singular}-states`, `查询${label}状态列表`, ['--project-group-no <no>', `--${camelToKebab(keyName)} <key>`], (a) => get([...assetRoot(a), plural, req(a, keyName), 'states'])),
|
|
204
|
+
cmd('asset', `${singular}-state`, `查询单个${label}状态`, ['--project-group-no <no>', `--${camelToKebab(keyName)} <key>`, '--state-key <key>'], (a) => get([...assetRoot(a), plural, req(a, keyName), 'states', req(a, 'stateKey')])),
|
|
205
|
+
cmd('asset', `upsert-${singular}-state`, `新增或更新${label}状态`, bodyCommandOptions(stateFields, [`--${camelToKebab(keyName)} <key>`]), (a) => post([...assetRoot(a), plural, req(a, keyName), 'states'], a, stateFields)),
|
|
206
|
+
cmd('asset', `delete-${singular}-state`, `逻辑删除${label}状态`, ['--project-group-no <no>', `--${camelToKebab(keyName)} <key>`, '--state-key <key>'], (a) => post([...assetRoot(a), plural, req(a, keyName), 'states', req(a, 'stateKey'), 'delete'])),
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function assetFilterOptions() {
|
|
211
|
+
return ['--project-group-no <no>', '--asset-type <type>', '--asset-id <id>', '--state-id <id>', '--resource-type <type>', '--resource-slot <slot>'];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function whitelistFilterOptions() {
|
|
215
|
+
return ['--project-group-no <no>', '--asset-type <type>', '--asset-id <id>', '--state-id <id>', '--resource-id <id>', '--platform <platform>'];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { commands };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const {
|
|
2
|
+
cmd,
|
|
3
|
+
get,
|
|
4
|
+
pick,
|
|
5
|
+
post,
|
|
6
|
+
put,
|
|
7
|
+
req,
|
|
8
|
+
scriptRoot,
|
|
9
|
+
} = require('../command-helpers');
|
|
10
|
+
|
|
11
|
+
const commands = [
|
|
12
|
+
cmd('script', 'get', '查询完整剧本', ['--project-group-no <no>'], (a) => get(scriptRoot(a))),
|
|
13
|
+
cmd('script', 'revision', '查询剧本版本', ['--project-group-no <no>'], (a) => get([...scriptRoot(a), 'revision'])),
|
|
14
|
+
cmd('script', 'replace', '整包替换剧本', ['--project-group-no <no>', '--input-file <file|->', '--user-id <id>'], (a) => put(scriptRoot(a), a)),
|
|
15
|
+
cmd('script', 'assets', '查询剧本资产', ['--project-group-no <no>', '--asset-kind <kind>'], (a) => get([...scriptRoot(a), 'assets'], pick(a, ['assetKind']))),
|
|
16
|
+
cmd('script', 'asset-states', '查询资产状态', ['--project-group-no <no>', '--asset-kind <kind>', '--asset-uid <uid>'], (a) => get([...scriptRoot(a), 'assets', req(a, 'assetKind'), req(a, 'assetUid'), 'states'])),
|
|
17
|
+
cmd('script', 'speakers', '查询发声源', ['--project-group-no <no>'], (a) => get([...scriptRoot(a), 'speakers'])),
|
|
18
|
+
cmd('script', 'episodes', '查询分集', ['--project-group-no <no>'], (a) => get([...scriptRoot(a), 'episodes'])),
|
|
19
|
+
cmd('script', 'scenes', '查询分集下场景', ['--project-group-no <no>', '--episode-uid <uid>'], (a) => get([...scriptRoot(a), 'episodes', req(a, 'episodeUid'), 'scenes'])),
|
|
20
|
+
cmd('script', 'scene', '查询单个场景', ['--project-group-no <no>', '--scene-uid <uid>'], (a) => get([...scriptRoot(a), 'scenes', req(a, 'sceneUid')])),
|
|
21
|
+
cmd('script', 'actions', '查询场景动作', ['--project-group-no <no>', '--scene-uid <uid>'], (a) => get([...scriptRoot(a), 'scenes', req(a, 'sceneUid'), 'actions'])),
|
|
22
|
+
cmd('script', 'issues', '查询校验问题', ['--project-group-no <no>', '--revision <revision>'], (a) => get([...scriptRoot(a), 'issues'], pick(a, ['revision']))),
|
|
23
|
+
cmd('script', 'save-validation-result', '写入校验结果', ['--project-group-no <no>', '--input-file <file|->'], (a) => post([...scriptRoot(a), 'validation-result'], a)),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
module.exports = { commands };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const {
|
|
2
|
+
cmd,
|
|
3
|
+
get,
|
|
4
|
+
post,
|
|
5
|
+
req,
|
|
6
|
+
videoRoot,
|
|
7
|
+
} = require('../command-helpers');
|
|
8
|
+
|
|
9
|
+
const commands = [
|
|
10
|
+
cmd('video', 'get', '查询项目完整视频产物', ['--project-group-no <no>'], (a) => get(videoRoot(a))),
|
|
11
|
+
cmd('video', 'episodes', '查询集列表', ['--project-group-no <no>'], (a) => get([...videoRoot(a), 'episodes'])),
|
|
12
|
+
cmd('video', 'episode', '查询单集', ['--project-group-no <no>', '--episode-id <id>'], (a) => get([...videoRoot(a), 'episodes', req(a, 'episodeId')])),
|
|
13
|
+
cmd('video', 'upsert-episode', '新增或更新集', ['--project-group-no <no>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes'], a)),
|
|
14
|
+
cmd('video', 'batch-episodes', '批量新增或更新集', ['--project-group-no <no>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', 'batch'], a)),
|
|
15
|
+
cmd('video', 'delete-episode', '逻辑删除集', ['--project-group-no <no>', '--episode-id <id>'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'delete'])),
|
|
16
|
+
cmd('video', 'scenes', '查询某集下的场列表', ['--project-group-no <no>', '--episode-id <id>'], (a) => get([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes'])),
|
|
17
|
+
cmd('video', 'scene', '查询单场', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>'], (a) => get([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId')])),
|
|
18
|
+
cmd('video', 'upsert-scene', '新增或更新场', ['--project-group-no <no>', '--episode-id <id>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes'], a)),
|
|
19
|
+
cmd('video', 'batch-scenes', '批量新增或更新场', ['--project-group-no <no>', '--episode-id <id>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', 'batch'], a)),
|
|
20
|
+
cmd('video', 'delete-scene', '逻辑删除场', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'delete'])),
|
|
21
|
+
cmd('video', 'clips', '查询某场下的 clip 列表', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>'], (a) => get([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips'])),
|
|
22
|
+
cmd('video', 'clip', '查询单个 clip', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>', '--clip-id <id>'], (a) => get([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips', req(a, 'clipId')])),
|
|
23
|
+
cmd('video', 'upsert-clip', '新增或更新 clip', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips'], a)),
|
|
24
|
+
cmd('video', 'batch-clips', '批量新增或更新 clip', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips', 'batch'], a)),
|
|
25
|
+
cmd('video', 'update-clip-video-urls', '更新 clip 视频链接', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>', '--clip-id <id>', '--input-file <file|->'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips', req(a, 'clipId'), 'video-urls'], a)),
|
|
26
|
+
cmd('video', 'delete-clip', '逻辑删除 clip', ['--project-group-no <no>', '--episode-id <id>', '--scene-id <id>', '--clip-id <id>'], (a) => post([...videoRoot(a), 'episodes', req(a, 'episodeId'), 'scenes', req(a, 'sceneId'), 'clips', req(a, 'clipId'), 'delete'])),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
module.exports = { commands };
|