@doubao-dev/plugin-cli 0.0.0-stage → 0.0.40-canary-dea0ddc73-20261026073950

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.

Potentially problematic release.


This version of @doubao-dev/plugin-cli might be problematic. Click here for more details.

@@ -0,0 +1,8 @@
1
+ import { listPluginCategories } from "./188.js";
2
+ import { ensureLogin } from "./command~1.js";
3
+ async function runCategories() {
4
+ await ensureLogin(false);
5
+ const { items = [] } = await listPluginCategories();
6
+ console.log(JSON.stringify(items, null, 2));
7
+ }
8
+ export { runCategories };
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { cancel, isCancel, select as prompts_select, text as prompts_text } from "@clack/prompts";
3
+ import { readFileSync } from "node:fs";
4
+ import { resolve } from "node:path";
5
+ import { z } from "zod";
6
+ import { upsertPluginDraft, getPluginDraft, errorMessage, terminal_log, submitApproval, withProgress, isInteractive, savePluginVersion, listPluginCategories } from "./188.js";
7
+ import { ensureLogin } from "./command~1.js";
8
+ const nonEmptyString = z.string().trim().min(1, '不能为空');
9
+ const skillMaterialsSchema = z.strictObject({
10
+ evaluation_report_url: nonEmptyString.regex(/^https?:\/\//i, '请输入 HTTP 或 HTTPS 报告地址'),
11
+ example_prompts: z.array(nonEmptyString).min(5).max(10)
12
+ });
13
+ const auditInputSchema = z.strictObject({
14
+ category_id: nonEmptyString.optional(),
15
+ modules: z.array(z.strictObject({
16
+ module_id: nonEmptyString,
17
+ mcp: z.strictObject({
18
+ test_credential: nonEmptyString
19
+ }).optional(),
20
+ skill: skillMaterialsSchema.optional()
21
+ })).default([])
22
+ });
23
+ function readAuditInput(projectRoot, path) {
24
+ const inputPath = resolve(projectRoot, path);
25
+ try {
26
+ return auditInputSchema.parse(JSON.parse(readFileSync(inputPath, 'utf8')));
27
+ } catch (cause) {
28
+ const message = cause instanceof SyntaxError ? '文件不是有效的 JSON' : cause instanceof Error ? cause.message : String(cause);
29
+ throw new Error(`无法读取审核材料 ${inputPath}:${message}`, {
30
+ cause
31
+ });
32
+ }
33
+ }
34
+ function resolveAuditModules(modules, inputs = []) {
35
+ const byId = new Map();
36
+ for (const input of inputs){
37
+ if (byId.has(input.module_id)) throw new Error(`审核材料中 module_id 重复:${input.module_id}`);
38
+ const module = modules.find((item)=>item.module_id === input.module_id);
39
+ if (!module) throw new Error(`草稿中不存在审核材料指定的模块:${input.module_id}`);
40
+ if ('MCP' === module.type && (!input.mcp || input.skill) || 'SKILL' === module.type && (!input.skill || input.mcp) || 'MCP' !== module.type && 'SKILL' !== module.type) throw new Error(`模块 ${input.module_id} 的审核材料须与草稿类型 ${module.type} 一致`);
41
+ byId.set(input.module_id, input);
42
+ }
43
+ const result = [];
44
+ for (const module of modules){
45
+ const input = module.module_id ? byId.get(module.module_id) : void 0;
46
+ const name = module.name || module.module_id;
47
+ if ('MCP' === module.type) {
48
+ if (!input?.mcp && !module.mcp?.masked_test_credential?.trim()) throw new Error(`连接器 ${name} 缺少测试账号或 Key,请通过 --input <path> 提供模块 ${module.module_id} 的 mcp.test_credential`);
49
+ if (input?.mcp && input.mcp.test_credential !== module.mcp?.masked_test_credential?.trim()) result.push({
50
+ module_id: module.module_id,
51
+ mcp: input.mcp
52
+ });
53
+ } else if ('SKILL' === module.type) {
54
+ const materials = input?.skill ?? {
55
+ evaluation_report_url: module.skill?.evaluation_report_url,
56
+ example_prompts: module.skill?.example_prompts
57
+ };
58
+ if (!skillMaterialsSchema.safeParse(materials).success) throw new Error(`Skill ${name} 缺少有效报告地址或 5–10 条示例提示词,请通过 --input <path> 提供模块 ${module.module_id} 的 skill 审核材料`);
59
+ if (input?.skill) result.push({
60
+ module_id: module.module_id,
61
+ skill: input.skill
62
+ });
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ async function promptMcpAuditInfo(module) {
68
+ const moduleName = module.name || module.module_id || 'MCP';
69
+ const testCredential = await prompts_text({
70
+ message: `请填写连接器 ${moduleName} 的测试账号或 Key`,
71
+ initialValue: module.mcp?.masked_test_credential,
72
+ validate: (value)=>nonEmptyString.safeParse(value).error?.issues[0]?.message
73
+ });
74
+ if (isCancel(testCredential)) return testCredential;
75
+ if (testCredential.trim() === module.mcp?.masked_test_credential?.trim()) return;
76
+ return {
77
+ module_id: module.module_id,
78
+ mcp: {
79
+ test_credential: testCredential.trim()
80
+ }
81
+ };
82
+ }
83
+ async function promptSkillAuditInfo(module) {
84
+ const skillName = module.name;
85
+ const reportUrl = await prompts_text({
86
+ message: `Skill ${skillName} 的评测报告地址`,
87
+ initialValue: module.skill?.evaluation_report_url,
88
+ validate: (value)=>skillMaterialsSchema.shape.evaluation_report_url.safeParse(value).error?.issues[0]?.message
89
+ });
90
+ if (isCancel(reportUrl)) return reportUrl;
91
+ terminal_log.info('请逐条填写 5–10 条覆盖核心场景的示例提示词,仅用于平台审核测试,不会向用户展示。');
92
+ const examplePrompts = [];
93
+ for(let index = 0; index < 10; index++){
94
+ const examplePrompt = await prompts_text({
95
+ message: `Skill ${skillName} 的第 ${index + 1} 条示例提示词`,
96
+ placeholder: index >= 5 ? '留空结束' : void 0,
97
+ initialValue: module.skill?.example_prompts?.[index],
98
+ validate: (value)=>{
99
+ if (index < 5 && !value.trim()) return '请填写示例提示词,至少需要 5 条';
100
+ }
101
+ });
102
+ if (isCancel(examplePrompt)) return examplePrompt;
103
+ if (!examplePrompt?.trim()) break;
104
+ examplePrompts.push(examplePrompt.trim());
105
+ }
106
+ return {
107
+ module_id: module.module_id,
108
+ skill: {
109
+ evaluation_report_url: reportUrl.trim(),
110
+ example_prompts: examplePrompts
111
+ }
112
+ };
113
+ }
114
+ async function runAudit(project, options = {}) {
115
+ await ensureLogin(false);
116
+ const { name } = project.plugin;
117
+ const input = void 0 !== options.input ? readAuditInput(project.root, options.input) : void 0;
118
+ const interactive = isInteractive() && !input;
119
+ const { draft } = await withProgress('查询草稿', ()=>getPluginDraft(name));
120
+ if (!draft) throw new Error(`服务端未找到 ${name} 的草稿,请先执行 dbx upload`);
121
+ const { items = [] } = await withProgress('查询意向分类', ()=>listPluginCategories());
122
+ const categoryOptions = items.flatMap(({ category_id, name })=>category_id ? [
123
+ {
124
+ value: category_id,
125
+ label: name || category_id
126
+ }
127
+ ] : []);
128
+ if (0 === categoryOptions.length) throw new Error('服务端未返回可选分类,暂时无法提交审核');
129
+ let categoryIds = input?.category_id ? [
130
+ input.category_id
131
+ ] : (draft.form?.categories ?? []).flatMap(({ category_id })=>category_id ? [
132
+ category_id
133
+ ] : []);
134
+ let modules = [];
135
+ if (interactive) {
136
+ const categoryId = await prompts_select({
137
+ message: '请选择意向分类',
138
+ options: categoryOptions,
139
+ initialValue: categoryOptions.find(({ value })=>categoryIds.includes(value))?.value
140
+ });
141
+ if (isCancel(categoryId)) {
142
+ cancel('已取消审核。');
143
+ process.exitCode = 130;
144
+ return;
145
+ }
146
+ categoryIds = [
147
+ categoryId
148
+ ];
149
+ for (const module of draft.modules ?? []){
150
+ if ('MCP' !== module.type && 'SKILL' !== module.type) continue;
151
+ const materials = 'MCP' === module.type ? await promptMcpAuditInfo(module) : await promptSkillAuditInfo(module);
152
+ if (isCancel(materials)) {
153
+ cancel('已取消审核。');
154
+ process.exitCode = 130;
155
+ return;
156
+ }
157
+ if (materials) modules.push(materials);
158
+ }
159
+ } else {
160
+ if (!categoryIds.length || categoryIds.some((id)=>!categoryOptions.some(({ value })=>value === id))) throw new Error('请先运行 dbx categories 查询审核类目,再通过 --input <path> 的 category_id 指定有效类目。');
161
+ modules = resolveAuditModules(draft.modules ?? [], input?.modules);
162
+ }
163
+ const { draft: updatedDraft } = await withProgress('保存审核信息', ()=>upsertPluginDraft({
164
+ name,
165
+ category_ids: categoryIds,
166
+ modules: modules.length > 0 ? modules : void 0
167
+ }));
168
+ if (!updatedDraft) throw new Error('审核信息保存失败:服务端未返回草稿');
169
+ const { version } = await savePluginVersion({
170
+ name,
171
+ request_id: randomUUID()
172
+ });
173
+ if (!version) throw new Error('保存版本失败:服务端未返回 version');
174
+ try {
175
+ const { approval_id: approvalId } = await withProgress('提交审核', ()=>submitApproval(name, version));
176
+ if (!approvalId) throw new Error('服务端未返回 approval_id');
177
+ terminal_log.success(`提交审核成功,审核 ID:${approvalId}`);
178
+ } catch (error) {
179
+ throw new Error(`提交审核失败:${errorMessage(error)}\n应用插件:${name}@${version}`, {
180
+ cause: error
181
+ });
182
+ }
183
+ }
184
+ export { runAudit };
@@ -0,0 +1,45 @@
1
+ import open_0 from "open";
2
+ import { runtime, startLogin, terminal_log, whoami, NotLoggedInError, login, isInteractive, completeLogin } from "./188.js";
3
+ async function runStatus() {
4
+ const result = await whoami(runtime.auth);
5
+ console.log(result.credentialError?.message ?? (result.loggedIn ? '已登录' : '尚未登录,请运行 dbx auth login'));
6
+ if (result.uid) console.log(`用户 ID:${result.uid}`);
7
+ }
8
+ async function ensureLogin(interactive = isInteractive()) {
9
+ const status = await whoami(runtime.auth);
10
+ if (status.credentialError) throw new Error(status.credentialError.message);
11
+ if (!status.loggedIn) {
12
+ if (!interactive) throw new NotLoggedInError();
13
+ await runLogin({});
14
+ }
15
+ }
16
+ async function runLogin(options) {
17
+ if (!isInteractive() && !options.begin && void 0 === options.complete) throw new Error('请使用 auth login --begin 发起授权,再使用 --complete <device-code> 完成授权');
18
+ if (void 0 !== options.complete && !options.complete.trim()) throw new Error('设备码不能为空');
19
+ if (options.begin) {
20
+ const result = await startLogin(runtime.auth);
21
+ showVerification(result);
22
+ terminal_log.step(`完成授权后运行:\n dbx auth login --complete ${result.deviceCode}`);
23
+ return;
24
+ }
25
+ terminal_log.step(options.complete ? '正在等待授权确认…' : '正在发起登录…');
26
+ const { expiresAt } = options.complete ? await completeLogin(options.complete.trim(), runtime.auth) : await login({
27
+ ...runtime.auth,
28
+ onVerification (info) {
29
+ showVerification(info);
30
+ terminal_log.step('正在等待浏览器授权…');
31
+ }
32
+ });
33
+ terminal_log.success('登录成功');
34
+ terminal_log.info(`凭证到期时间(本地):${new Date(1000 * expiresAt).toLocaleString('zh-CN', {
35
+ hour12: false
36
+ })}`);
37
+ }
38
+ function showVerification(info) {
39
+ terminal_log.info(`请在浏览器中完成授权(${info.expiresIn} 秒内)\n授权地址:${info.verificationUrl}\n用户验证码:${info.userCode}`);
40
+ if (!isInteractive()) return;
41
+ open_0(info.verificationUrl).catch(()=>{
42
+ terminal_log.warn('无法自动打开浏览器,请复制上方的授权地址完成授权。');
43
+ });
44
+ }
45
+ export { ensureLogin, runLogin, runStatus };
@@ -0,0 +1,14 @@
1
+ import { terminal_log, listPluginVersions, withdrawApproval, withProgress } from "./188.js";
2
+ import { ensureLogin } from "./command~1.js";
3
+ async function runCancelAudit(project) {
4
+ await ensureLogin(false);
5
+ const { name } = project.plugin;
6
+ const approvalId = await withProgress('查询审核记录', async ()=>{
7
+ const versions = await listPluginVersions(project.plugin);
8
+ return versions.find((version)=>version.current_approval?.approval_id)?.current_approval?.approval_id;
9
+ });
10
+ if (!approvalId) return void terminal_log.info(`应用插件 ${name} 暂无审核记录`);
11
+ await withProgress('取消提审', ()=>withdrawApproval(approvalId));
12
+ terminal_log.success(`已取消提审,审核 ID:${approvalId}`);
13
+ }
14
+ export { runCancelAudit };
@@ -0,0 +1,91 @@
1
+ import { spawn } from "node:child_process";
2
+ import { once } from "node:events";
3
+ import { join, resolve } from "node:path";
4
+ import { cancel, confirm as prompts_confirm, isCancel, text as prompts_text } from "@clack/prompts";
5
+ import { copySync } from "fs-extra/esm";
6
+ import { TEMPLATE_SUFFIX, STANDARD_TEMPLATE_DIRECTORY, DOUBAO_APP_DIRECTORY, projectNameSchema, runtime, TEMPLATES_DIRECTORY, loadAppModule, errorMessage, terminal_log, PLUGIN_NAME_PLACEHOLDER, loadPackage, isInteractive, syncProjectSkill } from "./188.js";
7
+ import * as __rspack_external_node_fs_1b05aee1 from "node:fs";
8
+ async function createProject(name, app) {
9
+ projectNameSchema.parse(name);
10
+ const directory = join(process.cwd(), name);
11
+ const existing = __rspack_external_node_fs_1b05aee1.lstatSync(directory, {
12
+ throwIfNoEntry: false
13
+ });
14
+ if (existing && (!existing.isDirectory() || __rspack_external_node_fs_1b05aee1.readdirSync(directory).length > 0)) throw new Error(`目标路径已存在且不是空目录:${directory}`);
15
+ const assets = app ? await loadAppModule('./assets') : void 0;
16
+ if (assets) await loadPackage(runtime.kit, new AbortController().signal);
17
+ const templates = join(import.meta.dirname, TEMPLATES_DIRECTORY);
18
+ copySync(join(templates, STANDARD_TEMPLATE_DIRECTORY), directory);
19
+ if (assets) copySync(assets.templatesRoot, directory);
20
+ for (const entry of __rspack_external_node_fs_1b05aee1.readdirSync(directory, {
21
+ recursive: true,
22
+ withFileTypes: true
23
+ }))if (entry.isFile() && entry.name.endsWith(TEMPLATE_SUFFIX)) {
24
+ const source = join(entry.parentPath, entry.name);
25
+ const target = source.slice(0, -TEMPLATE_SUFFIX.length);
26
+ __rspack_external_node_fs_1b05aee1.writeFileSync(target, __rspack_external_node_fs_1b05aee1.readFileSync(source, 'utf8').replaceAll(PLUGIN_NAME_PLACEHOLDER, name));
27
+ __rspack_external_node_fs_1b05aee1.unlinkSync(source);
28
+ }
29
+ __rspack_external_node_fs_1b05aee1.mkdirSync(join(directory, "skills"), {
30
+ recursive: true
31
+ });
32
+ if (app && 'example' !== name) __rspack_external_node_fs_1b05aee1.renameSync(join(directory, "skills", 'example'), join(directory, "skills", name));
33
+ await syncProjectSkill(directory);
34
+ }
35
+ async function runInit(name, options) {
36
+ let app = Boolean(options.app);
37
+ if (!name) {
38
+ if (!isInteractive()) throw new Error('请指定项目名称,例如 dbx init my-plugin');
39
+ const answer = await prompts_text({
40
+ message: '项目名称',
41
+ placeholder: 'my-plugin',
42
+ defaultValue: 'my-plugin',
43
+ validate: (value)=>projectNameSchema.safeParse(value || 'my-plugin').error?.issues[0]?.message
44
+ });
45
+ if (isCancel(answer)) {
46
+ cancel('已取消创建。');
47
+ process.exitCode = 130;
48
+ return;
49
+ }
50
+ name = answer;
51
+ if (!app) {
52
+ const choice = await prompts_confirm({
53
+ message: '添加应用前端(App)?',
54
+ initialValue: false
55
+ });
56
+ if (isCancel(choice)) {
57
+ cancel('已取消创建。');
58
+ process.exitCode = 130;
59
+ return;
60
+ }
61
+ app = choice;
62
+ }
63
+ }
64
+ await createProject(name, app);
65
+ terminal_log.success(`已创建 ${name}`);
66
+ if (!app) return;
67
+ if (false !== options.installDeps) {
68
+ const cwd = resolve(name, DOUBAO_APP_DIRECTORY);
69
+ terminal_log.info('正在使用 pnpm 安装应用依赖…');
70
+ try {
71
+ const child = spawn('pnpm', [
72
+ 'install'
73
+ ], {
74
+ cwd,
75
+ stdio: [
76
+ isInteractive() ? 'inherit' : 'ignore',
77
+ 'inherit',
78
+ 'inherit'
79
+ ]
80
+ });
81
+ const [code] = await once(child, 'close');
82
+ if (0 !== code) throw new Error(`pnpm 安装失败,退出码:${code}`);
83
+ terminal_log.success('应用依赖已安装');
84
+ } catch (error) {
85
+ terminal_log.warn(`应用依赖未安装:${errorMessage(error)}`);
86
+ terminal_log.info(`项目已保留,请进入 ${cwd} 后运行 pnpm install。`);
87
+ }
88
+ }
89
+ terminal_log.step(`运行 dbx dev --app --cwd ${name},打开 Web 模拟器后发送“搜索牛奶”。`);
90
+ }
91
+ export { runInit };
@@ -0,0 +1,191 @@
1
+ import chalk from "chalk";
2
+ import { TextPrompt } from "@clack/core";
3
+ import { isCancel, text as prompts_text } from "@clack/prompts";
4
+ import ora from "ora";
5
+ import { upsertPluginDraft, listPluginVersions, versionSchema, callHook, runtime, terminal_log, validateMaterial, uploadPackage, isInteractive, errorMessage, withProgress } from "./188.js";
6
+ import { ensureLogin } from "./command~1.js";
7
+ async function selectUploadVersion(plugin, specified) {
8
+ if (void 0 !== specified) return versionSchema.parse(specified);
9
+ if (!isInteractive()) throw new Error('请通过 --version 指定上传版本,例如 dbx upload --version 0.0.1');
10
+ let versions;
11
+ const spinner = ora({
12
+ text: ' 查询历史版本',
13
+ stream: process.stdout,
14
+ discardStdin: false
15
+ }).start();
16
+ try {
17
+ versions = await listPluginVersions(plugin);
18
+ spinner.succeed(versions.length ? ' 已获取历史版本' : ' 暂无历史版本');
19
+ } catch (error) {
20
+ spinner.stop();
21
+ if (errorMessage(error).includes('multiple plugins created by you have the same name')) throw new Error('存在同名插件,请在 plugin.json 中填写目标插件的 id 后重新上传。', {
22
+ cause: error
23
+ });
24
+ terminal_log.warn('暂时无法获取历史版本,可手动输入上传版本。');
25
+ const version = await prompts_text({
26
+ message: '请手动输入上传版本(首次上传可使用 0.0.1)',
27
+ validate: (value)=>versionSchema.safeParse(value).error?.issues[0]?.message
28
+ });
29
+ if (isCancel(version)) {
30
+ process.exitCode = 130;
31
+ return;
32
+ }
33
+ return version;
34
+ }
35
+ const { latest, choices } = versionChoices(versions.flatMap(({ version })=>{
36
+ const parsed = versionSchema.safeParse(version);
37
+ return parsed.success ? [
38
+ parsed.data
39
+ ] : [];
40
+ }));
41
+ let cursor = 0;
42
+ const selected = (input = '')=>input.trim() || choices[cursor].value;
43
+ const prompt = new TextPrompt({
44
+ initialValue: '',
45
+ validate: (input)=>versionSchema.safeParse(selected(input)).error?.issues[0]?.message,
46
+ render () {
47
+ if ('cancel' === this.state) return chalk.gray('└ 已取消上传');
48
+ if ('submit' === this.state) return `◇ 上传版本\n│ ${selected(this.value)}`;
49
+ const pageSize = Math.max(1, Math.min(6, (process.stdout.rows || 24) - 8));
50
+ const start = Math.floor(cursor / pageSize) * pageSize;
51
+ const input = (this.value ?? '').trim();
52
+ const rows = input ? [
53
+ `│ ${chalk.cyan('●')} 使用 ${input}`
54
+ ] : choices.slice(start, start + pageSize).map((choice, index)=>`│ ${index + start === cursor ? chalk.cyan('●') : '○'} ${choice.label}`);
55
+ return [
56
+ `${chalk.cyan('◆')} 上传版本${latest ? `(当前最高版本 ${latest})` : '(首次上传)'}`,
57
+ `│ > ${this.valueWithCursor}`,
58
+ '│',
59
+ ...rows,
60
+ 'error' === this.state ? `│ ${chalk.red(this.error)}` : `│ ${chalk.gray(input ? 'Enter 确认' : `↑↓ 选择或直接输入 · 第 ${Math.floor(cursor / pageSize) + 1}/${Math.ceil(choices.length / pageSize)} 页`)}`,
61
+ '└'
62
+ ].join('\n');
63
+ }
64
+ });
65
+ prompt.on('cursor', (key)=>{
66
+ if (!prompt.value && ('up' === key || 'down' === key)) cursor = (cursor + ('up' === key ? -1 : 1) + choices.length) % choices.length;
67
+ });
68
+ const value = await prompt.prompt();
69
+ if (isCancel(value)) {
70
+ process.exitCode = 130;
71
+ return;
72
+ }
73
+ return selected(value);
74
+ }
75
+ function versionChoices(versions) {
76
+ const choices = [
77
+ ...new Set(versions)
78
+ ];
79
+ if (!choices.length) return {
80
+ latest: '',
81
+ choices: [
82
+ {
83
+ value: '0.0.1',
84
+ label: '0.0.1'
85
+ }
86
+ ]
87
+ };
88
+ const [major, minor, patch] = choices[0].split('.').map(BigInt);
89
+ return {
90
+ latest: choices[0],
91
+ choices: [
92
+ {
93
+ value: `${major}.${minor}.${patch + 1n}`,
94
+ label: `${major}.${minor}.${patch + 1n} Patch`
95
+ },
96
+ {
97
+ value: `${major}.${minor + 1n}.0`,
98
+ label: `${major}.${minor + 1n}.0 Minor`
99
+ },
100
+ {
101
+ value: `${major + 1n}.0.0`,
102
+ label: `${major + 1n}.0.0 Major`
103
+ },
104
+ ...choices.map((value)=>({
105
+ value,
106
+ label: value
107
+ }))
108
+ ]
109
+ };
110
+ }
111
+ async function runUpload(project, options, hooks) {
112
+ await ensureLogin(false);
113
+ const { name, id } = project.plugin;
114
+ const identity = id ? {
115
+ ability_id: id
116
+ } : {
117
+ name
118
+ };
119
+ const version = await selectUploadVersion(project.plugin, options.version);
120
+ if (!version) return;
121
+ const ctx = {
122
+ project,
123
+ options: {
124
+ ...options,
125
+ version
126
+ }
127
+ };
128
+ let plan = {
129
+ version
130
+ };
131
+ for (const hook of hooks.modifyPackagePlan)plan = await callHook(hook, ctx, plan);
132
+ const content = await withProgress('打包项目', ()=>project.pack(plan));
133
+ const fileName = `${name}-${Date.now()}.zip`;
134
+ const packageId = await withProgress('上传应用插件包', async ()=>{
135
+ const { package_id: packageId } = await uploadPackage(fileName, content);
136
+ if (!packageId) throw new Error('上传失败:服务端未返回 package_id');
137
+ return packageId;
138
+ });
139
+ await withProgress('校验物料', async ()=>{
140
+ const validation = await validateMaterial({
141
+ package_id: packageId,
142
+ ...identity,
143
+ validate_list: [
144
+ 'PACKAGE_PARSE'
145
+ ]
146
+ });
147
+ if (!validation.passed) {
148
+ const issues = validation.issues?.map((issue)=>{
149
+ const location = [
150
+ issue.path,
151
+ issue.field_path
152
+ ].filter(Boolean).join(':');
153
+ const message = issue.message || issue.code || '未知问题';
154
+ return `- ${location ? `${location}:` : ''}${message}`;
155
+ }).join('\n');
156
+ throw new Error(`物料校验未通过${issues ? `\n${issues}` : ''}`);
157
+ }
158
+ });
159
+ const draft = await withProgress('保存草稿', async ()=>{
160
+ const { draft } = await upsertPluginDraft({
161
+ package_id: packageId,
162
+ ...identity
163
+ });
164
+ if (!draft?.name) throw new Error('草稿保存失败:服务端未返回 name');
165
+ return draft;
166
+ });
167
+ if (draft.ability_id) {
168
+ await project.updatePluginId(draft.ability_id);
169
+ terminal_log.success(`应用插件 ID:${draft.ability_id},已自动保存`);
170
+ }
171
+ terminal_log.success(`插件 ${chalk.cyan.bold(draft.name)} 上传成功`);
172
+ if (project.plugin.id) {
173
+ const url = new URL(runtime.consoleUrl);
174
+ url.search = new URLSearchParams({
175
+ ability: project.plugin.id,
176
+ resume: '1',
177
+ step: 'upload'
178
+ }).toString();
179
+ terminal_log.info(`平台草稿:${url.href}`);
180
+ }
181
+ for (const hook of hooks.afterUpload)try {
182
+ await hook.callback(ctx, {
183
+ packageId,
184
+ draft,
185
+ version: draft.version ?? plan.version
186
+ });
187
+ } catch (error) {
188
+ terminal_log.warn(`[${hook.pluginName}] 上传已完成,后续处理失败:${errorMessage(error)}\n可从上方平台草稿链接继续处理。`);
189
+ }
190
+ }
191
+ export { runUpload };