@fastgpt-plugin/cli 0.1.0-beta.4 → 0.1.0-beta.6
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/README.md +0 -1
- package/dist/index.js +1338 -294
- package/package.json +11 -8
- package/skills/cli-usage/SKILL.md +131 -0
- package/templates/tool/README.md +3 -3
- package/templates/tool/index.tool.ts +35 -0
- package/templates/tool/index.toolset.ts +109 -0
- package/templates/tool/package.json +1 -1
- package/templates/tool/config.ts +0 -28
- package/templates/tool/index.ts +0 -11
- package/templates/tool/src/schemas.ts +0 -9
- package/templates/tool/src/tool.test.ts +0 -17
- package/templates/tool/src/tool.ts +0 -8
- package/templates/tool-suite/README.md +0 -11
- package/templates/tool-suite/children/tool/config.ts +0 -29
- package/templates/tool-suite/children/tool/index.ts +0 -11
- package/templates/tool-suite/children/tool/logo.svg +0 -4
- package/templates/tool-suite/children/tool/src/schemas.ts +0 -9
- package/templates/tool-suite/children/tool/src/tool.test.ts +0 -37
- package/templates/tool-suite/children/tool/src/tool.ts +0 -8
- package/templates/tool-suite/config.ts +0 -19
- package/templates/tool-suite/index.ts +0 -6
- package/templates/tool-suite/logo.svg +0 -4
- package/templates/tool-suite/package.json +0 -21
- package/templates/tool-suite/tsconfig.json +0 -19
- package/templates/tool-suite/vitest.config.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -1,283 +1,242 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import path from "node:path";
|
|
2
|
+
import { registerHooks, stripTypeScriptTypes } from "node:module";
|
|
5
3
|
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { consola } from "consola/basic";
|
|
6
7
|
import { build } from "tsdown";
|
|
7
|
-
import
|
|
8
|
-
import { createHash } from "node:crypto";
|
|
8
|
+
import z from "zod";
|
|
9
9
|
import { input, select } from "@inquirer/prompts";
|
|
10
10
|
import { kebabCase } from "es-toolkit";
|
|
11
|
-
import {
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
12
|
+
import fs$1, { createWriteStream } from "node:fs";
|
|
13
|
+
import { Readable } from "stream";
|
|
14
|
+
import { Readable as Readable$1 } from "node:stream";
|
|
12
15
|
import { ZipFile } from "yazl";
|
|
16
|
+
import { Command } from "commander";
|
|
13
17
|
|
|
14
18
|
//#region src/helpers.ts
|
|
15
19
|
const logger = consola;
|
|
16
20
|
|
|
17
21
|
//#endregion
|
|
18
|
-
//#region
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
//#region ../../packages/domain/src/value-objects/i18n-string.vo.ts
|
|
23
|
+
const I18nLangSchema = z.enum([
|
|
24
|
+
"en",
|
|
25
|
+
"zh-CN",
|
|
26
|
+
"zh-Hant"
|
|
27
|
+
]);
|
|
28
|
+
const I18nLangEnum = I18nLangSchema.enum;
|
|
29
|
+
const I18nStringSchema = z.object({
|
|
30
|
+
[I18nLangEnum.en]: z.string(),
|
|
31
|
+
[I18nLangEnum["zh-CN"]]: z.string().optional(),
|
|
32
|
+
[I18nLangEnum["zh-Hant"]]: z.string().optional()
|
|
33
|
+
});
|
|
34
|
+
const I18nStringStrictSchema = z.object({
|
|
35
|
+
[I18nLangEnum.en]: z.string(),
|
|
36
|
+
[I18nLangEnum["zh-CN"]]: z.string(),
|
|
37
|
+
[I18nLangEnum["zh-Hant"]]: z.string()
|
|
38
|
+
});
|
|
21
39
|
|
|
22
40
|
//#endregion
|
|
23
|
-
//#region src/
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
//#region ../../packages/domain/src/value-objects/permission.vo.ts
|
|
42
|
+
const PluginPermissionEnumSchema = z.enum([
|
|
43
|
+
"userInfo:read",
|
|
44
|
+
"teamInfo:read",
|
|
45
|
+
"model:read",
|
|
46
|
+
"dataset:read",
|
|
47
|
+
"file-upload:allow"
|
|
48
|
+
]);
|
|
49
|
+
const PluginPermissionEnum = PluginPermissionEnumSchema.enum;
|
|
29
50
|
|
|
30
51
|
//#endregion
|
|
31
|
-
//#region src/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
//#region ../../packages/domain/src/value-objects/plugin.vo.ts
|
|
53
|
+
const PluginUniqueIdSchema = z.object({
|
|
54
|
+
pluginId: z.string(),
|
|
55
|
+
version: z.string(),
|
|
56
|
+
etag: z.string()
|
|
57
|
+
});
|
|
58
|
+
const PluginSourceSchema = z.literal("system").or(z.string());
|
|
59
|
+
const PluginTagListItemSchema = z.object({
|
|
60
|
+
id: z.string(),
|
|
61
|
+
name: I18nStringStrictSchema
|
|
62
|
+
});
|
|
63
|
+
const PluginTagListSchema = z.array(PluginTagListItemSchema);
|
|
64
|
+
const PluginRuntimeModeSchema = z.enum(["localPool", "serverless"]);
|
|
65
|
+
const PluginRuntimeModeEnum = PluginRuntimeModeSchema.enum;
|
|
66
|
+
const UserPluginIdSchema = z.object({
|
|
67
|
+
pluginId: z.string(),
|
|
68
|
+
version: z.string().optional(),
|
|
69
|
+
source: PluginSourceSchema.optional()
|
|
70
|
+
});
|
|
38
71
|
|
|
39
72
|
//#endregion
|
|
40
|
-
//#region src/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let aggregate = "";
|
|
81
|
-
for (const file of files) {
|
|
82
|
-
const fileHash = md5(await fs.readFile(file.abs));
|
|
83
|
-
aggregate += `${file.rel}:${fileHash}\n`;
|
|
84
|
-
}
|
|
85
|
-
return md5(aggregate);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* 计算工具集的 etag。
|
|
89
|
-
*
|
|
90
|
-
* 规则:
|
|
91
|
-
* - 首先为每个子工具单独计算 etag(同单工具规则)
|
|
92
|
-
* - 然后按子工具名称排序,拼接行:
|
|
93
|
-
* <tool-suite-id>/<child-name>:<child-etag>\n
|
|
94
|
-
* - 对拼接后的所有行再次计算 md5,得到工具集的 etag。
|
|
95
|
-
*/
|
|
96
|
-
async function computeToolSuiteEtag(options) {
|
|
97
|
-
const { rootDir, toolId } = options;
|
|
98
|
-
const children = [...options.children].sort();
|
|
99
|
-
const lines = [];
|
|
100
|
-
for (const child of children) {
|
|
101
|
-
const childEtag = await computeSingleToolEtag({ rootDir: path.join(rootDir, "children", child) });
|
|
102
|
-
lines.push(`${toolId}/${child}:${childEtag}`);
|
|
103
|
-
}
|
|
104
|
-
return md5(lines.length > 0 ? `${lines.join("\n")}\n` : "");
|
|
105
|
-
}
|
|
106
|
-
async function pathExists$1(p) {
|
|
107
|
-
try {
|
|
108
|
-
await fs.access(p);
|
|
109
|
-
return true;
|
|
110
|
-
} catch {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function md5(input) {
|
|
115
|
-
const hash = createHash("md5");
|
|
116
|
-
hash.update(input);
|
|
117
|
-
return hash.digest("hex");
|
|
118
|
-
}
|
|
73
|
+
//#region ../../packages/domain/src/entities/plugin-base.entity.ts
|
|
74
|
+
const PluginTagSchema = z.enum([
|
|
75
|
+
"tools",
|
|
76
|
+
"search",
|
|
77
|
+
"multimodal",
|
|
78
|
+
"communication",
|
|
79
|
+
"finance",
|
|
80
|
+
"design",
|
|
81
|
+
"productivity",
|
|
82
|
+
"news",
|
|
83
|
+
"entertainment",
|
|
84
|
+
"social",
|
|
85
|
+
"scientific",
|
|
86
|
+
"other"
|
|
87
|
+
]);
|
|
88
|
+
const PluginTagEnum = PluginTagSchema.enum;
|
|
89
|
+
const PluginTypeSchema = z.enum(["tool"]);
|
|
90
|
+
const PluginTypeEnum = PluginTypeSchema.enum;
|
|
91
|
+
const PluginStatusEnumSchema = z.enum([
|
|
92
|
+
"active",
|
|
93
|
+
"pending",
|
|
94
|
+
"disabled"
|
|
95
|
+
]);
|
|
96
|
+
const PluginStatusEnum = PluginStatusEnumSchema.enum;
|
|
97
|
+
const PluginBaseSchema = z.object({
|
|
98
|
+
pluginId: z.string(),
|
|
99
|
+
version: z.string(),
|
|
100
|
+
etag: z.string(),
|
|
101
|
+
type: PluginTypeSchema,
|
|
102
|
+
author: z.string().optional(),
|
|
103
|
+
repoUrl: z.string().optional(),
|
|
104
|
+
name: I18nStringSchema,
|
|
105
|
+
icon: z.string(),
|
|
106
|
+
tutorialUrl: z.url().optional(),
|
|
107
|
+
readmeUrl: z.url().optional(),
|
|
108
|
+
description: I18nStringSchema,
|
|
109
|
+
tags: z.array(PluginTagSchema).optional(),
|
|
110
|
+
versionDescription: I18nStringSchema.optional(),
|
|
111
|
+
permission: z.array(PluginPermissionEnumSchema).optional()
|
|
112
|
+
});
|
|
119
113
|
|
|
120
114
|
//#endregion
|
|
121
|
-
//#region src/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const needsToolId = !hasProperty(properties, "toolId");
|
|
173
|
-
const needsEtag = !hasProperty(properties, "etag");
|
|
174
|
-
const needsChildren = isToolSet && !hasProperty(properties, "children") && childrenList.length > 0;
|
|
175
|
-
if (!needsToolId && !needsEtag && !needsChildren) continue;
|
|
176
|
-
const lastProperty = properties[properties.length - 1];
|
|
177
|
-
const insertPos = lastProperty ? lastProperty.end : (objExpr.start ?? 0) + 1;
|
|
178
|
-
const newProperties = [];
|
|
179
|
-
if (needsToolId) newProperties.push(`toolId: '${toolId}'`);
|
|
180
|
-
if (needsEtag) {
|
|
181
|
-
const etagValue = isToolSet && toolSuiteEtag ? toolSuiteEtag : singleToolEtag;
|
|
182
|
-
newProperties.push(`etag: '${etagValue}'`);
|
|
183
|
-
}
|
|
184
|
-
if (needsChildren) {
|
|
185
|
-
const childrenArray = childrenList.map((child) => child).join(", ");
|
|
186
|
-
newProperties.push(`children: [${childrenArray}]`);
|
|
187
|
-
const importStatements = childrenList.map((child) => `import ${child} from './children/${child}';`).join("\n");
|
|
188
|
-
modifications.push({
|
|
189
|
-
start: 0,
|
|
190
|
-
end: 0,
|
|
191
|
-
content: importStatements + "\n"
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
if (newProperties.length > 0) {
|
|
195
|
-
const content = (lastProperty ? ",\n " : "\n ") + newProperties.join(",\n ");
|
|
196
|
-
modifications.push({
|
|
197
|
-
start: insertPos,
|
|
198
|
-
end: insertPos,
|
|
199
|
-
content
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (modifications.length === 0) return {
|
|
204
|
-
code: sourceCode,
|
|
205
|
-
hasToolId: false
|
|
206
|
-
};
|
|
207
|
-
return {
|
|
208
|
-
code: applyModifications(sourceCode, modifications),
|
|
209
|
-
hasToolId: true
|
|
210
|
-
};
|
|
211
|
-
}
|
|
115
|
+
//#region ../../packages/domain/src/entities/tool.entity.ts
|
|
116
|
+
const ToolSetChildItemSchema = z.object({
|
|
117
|
+
id: z.string(),
|
|
118
|
+
name: I18nStringSchema,
|
|
119
|
+
description: I18nStringSchema.optional(),
|
|
120
|
+
icon: z.string(),
|
|
121
|
+
toolDescription: z.string(),
|
|
122
|
+
inputSchema: z.any(),
|
|
123
|
+
outputSchema: z.any()
|
|
124
|
+
});
|
|
125
|
+
const ToolSchema = z.object({
|
|
126
|
+
...PluginBaseSchema.shape,
|
|
127
|
+
type: z.literal(PluginTypeEnum.tool),
|
|
128
|
+
toolDescription: z.string(),
|
|
129
|
+
inputSchema: z.any().optional(),
|
|
130
|
+
outputSchema: z.any().optional(),
|
|
131
|
+
secretSchema: z.any().optional(),
|
|
132
|
+
children: z.array(ToolSetChildItemSchema).optional()
|
|
133
|
+
});
|
|
134
|
+
const ToolSetSchema = z.object({
|
|
135
|
+
...PluginBaseSchema.shape,
|
|
136
|
+
type: z.literal(PluginTypeEnum.tool),
|
|
137
|
+
meta: z.object({
|
|
138
|
+
secretSchema: z.any().optional(),
|
|
139
|
+
toolDescription: z.string()
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region ../../packages/domain/src/value-objects/plugin/plugin-manifest.vo.ts
|
|
145
|
+
const PluginManifestBaseSchema = z.object({ ...PluginBaseSchema.omit({
|
|
146
|
+
etag: true,
|
|
147
|
+
readmeUrl: true
|
|
148
|
+
}).shape });
|
|
149
|
+
const ToolManifestSchema = z.object({
|
|
150
|
+
...PluginManifestBaseSchema.shape,
|
|
151
|
+
type: z.literal(PluginTypeEnum.tool),
|
|
152
|
+
toolDescription: z.string(),
|
|
153
|
+
inputSchema: z.any().optional(),
|
|
154
|
+
outputSchema: z.any().optional(),
|
|
155
|
+
secretSchema: z.any(),
|
|
156
|
+
children: z.array(z.object({
|
|
157
|
+
id: z.string(),
|
|
158
|
+
description: I18nStringSchema,
|
|
159
|
+
name: I18nStringSchema,
|
|
160
|
+
toolDescription: z.string(),
|
|
161
|
+
icon: z.string(),
|
|
162
|
+
inputSchema: z.any(),
|
|
163
|
+
outputSchema: z.any()
|
|
164
|
+
})).min(1).optional()
|
|
165
|
+
});
|
|
212
166
|
|
|
213
167
|
//#endregion
|
|
214
168
|
//#region src/build/index.ts
|
|
215
169
|
/**
|
|
216
170
|
* 核心构建逻辑:给定入口目录和输出目录,完成一次工具构建。
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
171
|
+
*
|
|
172
|
+
* 输出:
|
|
173
|
+
* dist/index.js
|
|
174
|
+
* dist/manifest.json
|
|
175
|
+
* dist/logo.* / dist/<childId>.logo.*
|
|
176
|
+
* dist/README.md(可选)
|
|
177
|
+
* dist/assets/**(可选)
|
|
220
178
|
*/
|
|
221
179
|
async function buildToolPackage(options) {
|
|
222
180
|
const entryDir = path.resolve(options.entry);
|
|
223
|
-
|
|
224
|
-
const configPath = path.join(entryDir, "config.ts");
|
|
225
|
-
await assertPathExists$1(configPath, `找不到 config.ts 文件: ${configPath}`);
|
|
181
|
+
const outputDir = path.resolve(options.output);
|
|
226
182
|
const indexPath = path.join(entryDir, "index.ts");
|
|
227
|
-
await assertPathExists$1(indexPath, `找不到 index.ts 文件: ${indexPath}`);
|
|
228
183
|
const tempDir = path.join(entryDir, ".build-temp");
|
|
229
|
-
await
|
|
184
|
+
await assertPathExists$1(entryDir, `入口目录不存在: ${entryDir}`);
|
|
185
|
+
await assertPathExists$1(indexPath, `找不到 index.ts 文件: ${indexPath}`);
|
|
230
186
|
const tStart = Date.now();
|
|
231
|
-
let tConfig = 0;
|
|
232
187
|
let tCopy = 0;
|
|
233
188
|
let tBuild = 0;
|
|
189
|
+
let tAssemble = 0;
|
|
234
190
|
try {
|
|
235
|
-
|
|
236
|
-
const transformed = await transformToolConfig({
|
|
237
|
-
sourceCode: await fs.readFile(configPath, "utf-8"),
|
|
238
|
-
filePath: configPath
|
|
239
|
-
});
|
|
240
|
-
await fs.writeFile(path.join(tempDir, "config.ts"), transformed.code, "utf-8");
|
|
241
|
-
tConfig = Date.now() - tConfigStart;
|
|
191
|
+
await resetDir(tempDir);
|
|
242
192
|
const tCopyStart = Date.now();
|
|
243
|
-
await copySourceFiles(entryDir, tempDir
|
|
193
|
+
await copySourceFiles(entryDir, tempDir);
|
|
244
194
|
tCopy = Date.now() - tCopyStart;
|
|
245
|
-
|
|
246
|
-
await ensureDir$1(outputDir);
|
|
195
|
+
await resetDir(outputDir);
|
|
247
196
|
const tempIndexPath = path.join(tempDir, "index.ts");
|
|
248
197
|
const tBuildStart = Date.now();
|
|
249
198
|
await build({
|
|
250
|
-
|
|
199
|
+
deps: { alwaysBundle: [
|
|
200
|
+
"*",
|
|
201
|
+
"*/*",
|
|
202
|
+
"@*/*"
|
|
203
|
+
] },
|
|
204
|
+
entry: { index: tempIndexPath },
|
|
251
205
|
outDir: outputDir,
|
|
252
206
|
format: [options.format],
|
|
253
207
|
clean: true,
|
|
254
208
|
minify: options.minify,
|
|
255
|
-
inlineOnly: false,
|
|
256
209
|
nodeProtocol: true,
|
|
257
210
|
platform: "node",
|
|
258
211
|
target: "node22",
|
|
259
212
|
dts: false,
|
|
260
213
|
treeshake: true,
|
|
261
|
-
noExternal: ["*"],
|
|
262
214
|
outExtensions: () => ({
|
|
263
215
|
dts: ".d.ts",
|
|
264
216
|
js: ".js"
|
|
265
|
-
})
|
|
217
|
+
}),
|
|
218
|
+
outputOptions: { codeSplitting: false }
|
|
266
219
|
});
|
|
267
220
|
tBuild = Date.now() - tBuildStart;
|
|
268
|
-
const
|
|
221
|
+
const tAssembleStart = Date.now();
|
|
222
|
+
const plan = await createBuildPlan(entryDir, await loadPackableTool(path.join(outputDir, "index.js")));
|
|
223
|
+
await fs.writeFile(path.join(outputDir, "manifest.json"), JSON.stringify(plan.manifest, null, 2), "utf-8");
|
|
224
|
+
await copyLogoFiles(outputDir, plan.logoFiles);
|
|
225
|
+
await copyOptionalStaticFiles(entryDir, outputDir);
|
|
226
|
+
tAssemble = Date.now() - tAssembleStart;
|
|
227
|
+
const files = await collectRelativeFiles(outputDir);
|
|
269
228
|
const tTotal = Date.now() - tStart;
|
|
270
229
|
logger.info([
|
|
271
230
|
"构建阶段耗时统计:",
|
|
272
|
-
`•
|
|
273
|
-
`•
|
|
274
|
-
`•
|
|
275
|
-
`• 总耗时:
|
|
231
|
+
`• 源码复制: ${formatDuration(tCopy)}`,
|
|
232
|
+
`• tsdown 构建: ${formatDuration(tBuild)}`,
|
|
233
|
+
`• 产物组装: ${formatDuration(tAssemble)}`,
|
|
234
|
+
`• 总耗时: ${formatDuration(tTotal)}`
|
|
276
235
|
].join("\n"));
|
|
277
236
|
return {
|
|
278
237
|
entryDir,
|
|
279
238
|
outputDir,
|
|
280
|
-
files
|
|
239
|
+
files
|
|
281
240
|
};
|
|
282
241
|
} finally {
|
|
283
242
|
try {
|
|
@@ -288,12 +247,69 @@ async function buildToolPackage(options) {
|
|
|
288
247
|
} catch {}
|
|
289
248
|
}
|
|
290
249
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
250
|
+
async function loadPackableTool(indexJsPath) {
|
|
251
|
+
await assertPathExists$1(indexJsPath, `找不到编译产物 index.js:${indexJsPath}`);
|
|
252
|
+
const tool = (await import(`${pathToFileURL(indexJsPath).href}?t=${Date.now()}`)).default;
|
|
253
|
+
if (!isPackableToolExport$1(tool)) throw new Error("index.ts 的 default export 必须是 defineTool()/defineToolSet() 返回的 factory-sdk 实例");
|
|
254
|
+
return tool;
|
|
255
|
+
}
|
|
256
|
+
async function createBuildPlan(entryDir, tool) {
|
|
257
|
+
const rootLogoPath = await findRootLogoFile(entryDir);
|
|
258
|
+
if (!rootLogoPath) throw new Error(`找不到主 logo 文件,请在 ${entryDir} 下提供 logo.*`);
|
|
259
|
+
const rootLogoName = path.basename(rootLogoPath);
|
|
260
|
+
const logoFiles = new Map([[rootLogoName, rootLogoPath]]);
|
|
261
|
+
const userManifest = tool.getUserToolManifest();
|
|
262
|
+
const secretSchema = z.toJSONSchema(tool.getSecretSchema());
|
|
263
|
+
const toolDescription = pickToolDescription$1(getOptionalString$1(userManifest.toolDescription), userManifest.description);
|
|
264
|
+
const childManifests = tool.getChildManifests();
|
|
265
|
+
const manifestCandidate = {
|
|
266
|
+
...userManifest,
|
|
267
|
+
type: "tool",
|
|
268
|
+
icon: rootLogoName,
|
|
269
|
+
toolDescription,
|
|
270
|
+
secretSchema
|
|
271
|
+
};
|
|
272
|
+
if (childManifests.length === 0) {
|
|
273
|
+
const handler = tool.getToolHandler();
|
|
274
|
+
if (!handler) throw new Error("default export 未注册任何工具 handler");
|
|
275
|
+
manifestCandidate.inputSchema = z.toJSONSchema(handler.inputSchema);
|
|
276
|
+
manifestCandidate.outputSchema = z.toJSONSchema(handler.outputSchema);
|
|
277
|
+
} else manifestCandidate.children = await Promise.all(childManifests.map(async (child) => {
|
|
278
|
+
const handler = tool.getToolHandler(child.id);
|
|
279
|
+
if (!handler) throw new Error(`找不到子工具 handler: ${child.id}`);
|
|
280
|
+
const childLogoPath = await findChildLogoFile(entryDir, child.id) ?? rootLogoPath;
|
|
281
|
+
const childLogoName = path.basename(childLogoPath);
|
|
282
|
+
logoFiles.set(childLogoName, childLogoPath);
|
|
283
|
+
return {
|
|
284
|
+
id: child.id,
|
|
285
|
+
description: child.description,
|
|
286
|
+
name: child.name,
|
|
287
|
+
icon: childLogoName,
|
|
288
|
+
toolDescription: pickToolDescription$1(child.toolDescription, child.description),
|
|
289
|
+
inputSchema: z.toJSONSchema(handler.inputSchema),
|
|
290
|
+
outputSchema: z.toJSONSchema(handler.outputSchema)
|
|
291
|
+
};
|
|
292
|
+
}));
|
|
293
|
+
return {
|
|
294
|
+
manifest: ToolManifestSchema.parse(manifestCandidate),
|
|
295
|
+
logoFiles: [...logoFiles.entries()].map(([fileName, sourcePath]) => ({
|
|
296
|
+
fileName,
|
|
297
|
+
sourcePath
|
|
298
|
+
}))
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
async function copyLogoFiles(outputDir, logoFiles) {
|
|
302
|
+
await Promise.all(logoFiles.map(async ({ fileName, sourcePath }) => {
|
|
303
|
+
await fs.copyFile(sourcePath, path.join(outputDir, fileName));
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
async function copyOptionalStaticFiles(entryDir, outputDir) {
|
|
307
|
+
const readmePath = path.join(entryDir, "README.md");
|
|
308
|
+
if (await pathExists$1(readmePath)) await fs.copyFile(readmePath, path.join(outputDir, "README.md"));
|
|
309
|
+
const assetsDir = path.join(entryDir, "assets");
|
|
310
|
+
if (await pathExists$1(assetsDir)) await copyDirectory(assetsDir, path.join(outputDir, "assets"));
|
|
311
|
+
}
|
|
312
|
+
async function copySourceFiles(sourceDir, targetDir) {
|
|
297
313
|
const files = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
298
314
|
for (const file of files) {
|
|
299
315
|
if (file.name === "node_modules" || file.name === "dist" || file.name.startsWith(".build-")) continue;
|
|
@@ -301,21 +317,88 @@ async function copySourceFiles(sourceDir, targetDir, rootDir) {
|
|
|
301
317
|
const targetPath = path.join(targetDir, file.name);
|
|
302
318
|
if (file.isDirectory()) {
|
|
303
319
|
await ensureDir$1(targetPath);
|
|
304
|
-
await copySourceFiles(sourcePath, targetPath
|
|
305
|
-
} else if (file.isFile())
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
await copySourceFiles(sourcePath, targetPath);
|
|
321
|
+
} else if (file.isFile()) await fs.copyFile(sourcePath, targetPath);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function collectRelativeFiles(dir, baseDir = dir) {
|
|
325
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
326
|
+
const files = [];
|
|
327
|
+
for (const entry of entries) {
|
|
328
|
+
const fullPath = path.join(dir, entry.name);
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
files.push(...await collectRelativeFiles(fullPath, baseDir));
|
|
331
|
+
continue;
|
|
313
332
|
}
|
|
333
|
+
if (entry.isFile()) files.push(path.relative(baseDir, fullPath));
|
|
334
|
+
}
|
|
335
|
+
return files.sort();
|
|
336
|
+
}
|
|
337
|
+
async function findRootLogoFile(entryDir) {
|
|
338
|
+
return findMatchingFile(entryDir, /^logo\./i);
|
|
339
|
+
}
|
|
340
|
+
async function findChildLogoFile(entryDir, childId) {
|
|
341
|
+
const escapedChildId = escapeRegExp(childId);
|
|
342
|
+
return findMatchingFile(entryDir, new RegExp(`^${escapedChildId}\\.logo\\.`, "i"));
|
|
343
|
+
}
|
|
344
|
+
async function findMatchingFile(dir, pattern) {
|
|
345
|
+
try {
|
|
346
|
+
const matched = (await fs.readdir(dir, { withFileTypes: true })).find((entry) => entry.isFile() && pattern.test(entry.name));
|
|
347
|
+
return matched ? path.join(dir, matched.name) : null;
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function copyDirectory(sourceDir, targetDir) {
|
|
353
|
+
await ensureDir$1(targetDir);
|
|
354
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
355
|
+
for (const entry of entries) {
|
|
356
|
+
const sourcePath = path.join(sourceDir, entry.name);
|
|
357
|
+
const targetPath = path.join(targetDir, entry.name);
|
|
358
|
+
if (entry.isDirectory()) await copyDirectory(sourcePath, targetPath);
|
|
359
|
+
else if (entry.isFile()) await fs.copyFile(sourcePath, targetPath);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function isPackableToolExport$1(value) {
|
|
363
|
+
return Boolean(value && typeof value === "object" && "getUserToolManifest" in value && typeof value.getUserToolManifest === "function" && "getSecretSchema" in value && typeof value.getSecretSchema === "function" && "getToolHandler" in value && typeof value.getToolHandler === "function" && "getChildManifests" in value && typeof value.getChildManifests === "function");
|
|
364
|
+
}
|
|
365
|
+
function pickToolDescription$1(explicitDescription, fallbackSource) {
|
|
366
|
+
if (explicitDescription && explicitDescription.trim().length > 0) return explicitDescription;
|
|
367
|
+
if (fallbackSource && typeof fallbackSource === "object") {
|
|
368
|
+
const localized = fallbackSource;
|
|
369
|
+
const preferred = [
|
|
370
|
+
localized["zh-CN"],
|
|
371
|
+
localized.en,
|
|
372
|
+
...Object.values(localized)
|
|
373
|
+
].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
374
|
+
if (typeof preferred === "string") return preferred;
|
|
375
|
+
}
|
|
376
|
+
return "";
|
|
377
|
+
}
|
|
378
|
+
function getOptionalString$1(value) {
|
|
379
|
+
return typeof value === "string" ? value : void 0;
|
|
380
|
+
}
|
|
381
|
+
function escapeRegExp(value) {
|
|
382
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
383
|
+
}
|
|
384
|
+
async function pathExists$1(p) {
|
|
385
|
+
try {
|
|
386
|
+
await fs.access(p);
|
|
387
|
+
return true;
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
314
390
|
}
|
|
315
391
|
}
|
|
316
392
|
async function ensureDir$1(dir) {
|
|
317
393
|
await fs.mkdir(dir, { recursive: true });
|
|
318
394
|
}
|
|
395
|
+
async function resetDir(dir) {
|
|
396
|
+
await fs.rm(dir, {
|
|
397
|
+
recursive: true,
|
|
398
|
+
force: true
|
|
399
|
+
});
|
|
400
|
+
await ensureDir$1(dir);
|
|
401
|
+
}
|
|
319
402
|
async function assertPathExists$1(p, message) {
|
|
320
403
|
try {
|
|
321
404
|
await fs.access(p);
|
|
@@ -328,6 +411,15 @@ function formatDuration(ms) {
|
|
|
328
411
|
return `${(ms / 1e3).toFixed(2)}s`;
|
|
329
412
|
}
|
|
330
413
|
|
|
414
|
+
//#endregion
|
|
415
|
+
//#region src/commands/base.ts
|
|
416
|
+
/**
|
|
417
|
+
* 所有子命令的基类:
|
|
418
|
+
* - 统一命令接口类型
|
|
419
|
+
* - 强制实现 register 方法,用于向 Commander 注册自身
|
|
420
|
+
*/
|
|
421
|
+
var BaseCommand = class {};
|
|
422
|
+
|
|
331
423
|
//#endregion
|
|
332
424
|
//#region src/commands/build.ts
|
|
333
425
|
var BuildCommand = class extends BaseCommand {
|
|
@@ -363,6 +455,100 @@ var BuildCommand = class extends BaseCommand {
|
|
|
363
455
|
}
|
|
364
456
|
};
|
|
365
457
|
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/check/index.ts
|
|
460
|
+
/**
|
|
461
|
+
* 检查构建产物的正确性:
|
|
462
|
+
* - dist/index.js 存在
|
|
463
|
+
* - dist/manifest.json 存在且通过 Schema 校验
|
|
464
|
+
* - manifest.json 引用的 logo 文件存在
|
|
465
|
+
*/
|
|
466
|
+
async function checkBuildOutput(options) {
|
|
467
|
+
const outputDir = path.resolve(options.output);
|
|
468
|
+
const errors = [];
|
|
469
|
+
const warnings = [];
|
|
470
|
+
const indexJsPath = path.join(outputDir, "index.js");
|
|
471
|
+
if (!await pathExists(indexJsPath)) errors.push(`缺少构建产物 dist/index.js:${indexJsPath}`);
|
|
472
|
+
const manifestPath = path.join(outputDir, "manifest.json");
|
|
473
|
+
const manifestContent = await readFileSafe(manifestPath);
|
|
474
|
+
if (manifestContent === null) errors.push(`缺少 dist/manifest.json:${manifestPath}`);
|
|
475
|
+
else {
|
|
476
|
+
let rawManifest;
|
|
477
|
+
try {
|
|
478
|
+
rawManifest = JSON.parse(manifestContent);
|
|
479
|
+
} catch (e) {
|
|
480
|
+
errors.push(`manifest.json 解析失败(JSON 语法错误):${e.message}`);
|
|
481
|
+
}
|
|
482
|
+
if (rawManifest !== void 0) {
|
|
483
|
+
const result = ToolManifestSchema.safeParse(rawManifest);
|
|
484
|
+
if (!result.success) for (const issue of result.error.issues) errors.push(`manifest.json 校验失败 [${issue.path.join(".")}]: ${issue.message}`);
|
|
485
|
+
else await validateManifestAssets(result.data, outputDir, errors, warnings);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
ok: errors.length === 0,
|
|
490
|
+
errors,
|
|
491
|
+
warnings
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
async function validateManifestAssets(manifest, outputDir, errors, warnings) {
|
|
495
|
+
if (!await pathExists(path.join(outputDir, manifest.icon))) errors.push(`manifest.json 引用的主 logo 不存在:${manifest.icon}`);
|
|
496
|
+
if (manifest.children && manifest.children.length > 0) {
|
|
497
|
+
for (const child of manifest.children) if (!await pathExists(path.join(outputDir, child.icon))) errors.push(`manifest.json 中子工具 "${child.id}" 的 logo 不存在:${child.icon}`);
|
|
498
|
+
}
|
|
499
|
+
if (!await pathExists(path.join(outputDir, "README.md"))) warnings.push("dist/README.md 不存在");
|
|
500
|
+
}
|
|
501
|
+
async function readFileSafe(filePath) {
|
|
502
|
+
try {
|
|
503
|
+
return await fs.readFile(filePath, "utf-8");
|
|
504
|
+
} catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function pathExists(p) {
|
|
509
|
+
try {
|
|
510
|
+
await fs.access(p);
|
|
511
|
+
return true;
|
|
512
|
+
} catch {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region src/commands/check.ts
|
|
519
|
+
var CheckCommand = class extends BaseCommand {
|
|
520
|
+
register(parent) {
|
|
521
|
+
parent.command("check").description("检查构建产物的正确性(manifest.json、config.json、index.js)").option("-e, --entry <path>", "工具源码目录", process.cwd()).option("-o, --output <path>", "构建输出目录", "./dist").action(async (opts) => {
|
|
522
|
+
await this.run(opts);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async run(options) {
|
|
526
|
+
logger.info(`检查目录: ${options.entry}`);
|
|
527
|
+
logger.info(`产物目录: ${options.output}`);
|
|
528
|
+
const result = await checkBuildOutput(options);
|
|
529
|
+
if (result.warnings.length > 0) for (const w of result.warnings) logger.warn(w);
|
|
530
|
+
if (result.errors.length > 0) {
|
|
531
|
+
logger.error(`检查失败,共 ${result.errors.length} 个错误:`);
|
|
532
|
+
for (const e of result.errors) logger.error(` ✗ ${e}`);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
logger.success("检查通过,构建产物格式正确");
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region package.json
|
|
541
|
+
var name = "@fastgpt-plugin/cli";
|
|
542
|
+
var version = "0.1.0-beta.6";
|
|
543
|
+
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/constants.ts
|
|
546
|
+
const CLI_NAME = name;
|
|
547
|
+
const CLI_VERSION = version;
|
|
548
|
+
const DEFAULT_PLUGIN_NAME = "./packages/my-tool";
|
|
549
|
+
const DEFAULT_PLUGIN_DESCRIPTION = "This is a FastGPT plugin";
|
|
550
|
+
const TOOL_TEMPLATES_DIR = path.join(import.meta.dirname, "../templates");
|
|
551
|
+
|
|
366
552
|
//#endregion
|
|
367
553
|
//#region src/prompts/create/deps.ts
|
|
368
554
|
const defaultDeps = {
|
|
@@ -460,9 +646,9 @@ var CreateCommand = class extends BaseCommand {
|
|
|
460
646
|
}
|
|
461
647
|
async run(options) {
|
|
462
648
|
const targetDir = path.resolve(options.cwd, options.name);
|
|
463
|
-
const templateDir = path.join(TOOL_TEMPLATES_DIR,
|
|
649
|
+
const templateDir = path.join(TOOL_TEMPLATES_DIR, "tool");
|
|
464
650
|
const files = await this.collectTemplateFiles(templateDir);
|
|
465
|
-
const description = options.description ??
|
|
651
|
+
const description = options.description ?? "This is a FastGPT plugin";
|
|
466
652
|
await this.ensureDir(targetDir);
|
|
467
653
|
for (const rel of files) {
|
|
468
654
|
const templatePath = path.join(templateDir, rel);
|
|
@@ -472,6 +658,7 @@ var CreateCommand = class extends BaseCommand {
|
|
|
472
658
|
const content = this.applyPlaceholders(raw, options.name, description);
|
|
473
659
|
await fs.writeFile(filePath, content, "utf-8");
|
|
474
660
|
}
|
|
661
|
+
await this.finalizeIndexTemplate(targetDir, options.type);
|
|
475
662
|
logger.success(`创建插件项目: ${options.name} (${options.type})`, { cwd: options.cwd });
|
|
476
663
|
}
|
|
477
664
|
applyPlaceholders(text, name, description) {
|
|
@@ -495,58 +682,906 @@ var CreateCommand = class extends BaseCommand {
|
|
|
495
682
|
await walk(root);
|
|
496
683
|
return result.sort();
|
|
497
684
|
}
|
|
685
|
+
async finalizeIndexTemplate(targetDir, type) {
|
|
686
|
+
const toolIndexPath = path.join(targetDir, "index.tool.ts");
|
|
687
|
+
const toolSetIndexPath = path.join(targetDir, "index.toolset.ts");
|
|
688
|
+
const finalIndexPath = path.join(targetDir, "index.ts");
|
|
689
|
+
const sourcePath = type === "tool-suite" ? toolSetIndexPath : toolIndexPath;
|
|
690
|
+
const cleanupTargets = [toolIndexPath, toolSetIndexPath];
|
|
691
|
+
await fs.rename(sourcePath, finalIndexPath);
|
|
692
|
+
await Promise.all(cleanupTargets.map(async (filePath) => {
|
|
693
|
+
if (filePath === sourcePath) return;
|
|
694
|
+
await fs.rm(filePath, { force: true });
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
498
697
|
async ensureDir(dir) {
|
|
499
698
|
await fs.mkdir(dir, { recursive: true });
|
|
500
699
|
}
|
|
501
700
|
};
|
|
502
701
|
|
|
702
|
+
//#endregion
|
|
703
|
+
//#region src/debug/import-hooks.ts
|
|
704
|
+
const SOURCE_EXTENSIONS = [
|
|
705
|
+
".ts",
|
|
706
|
+
".tsx",
|
|
707
|
+
".mts",
|
|
708
|
+
".cts"
|
|
709
|
+
];
|
|
710
|
+
const RESOLVE_EXTENSIONS = [
|
|
711
|
+
"",
|
|
712
|
+
...SOURCE_EXTENSIONS,
|
|
713
|
+
".js",
|
|
714
|
+
".mjs",
|
|
715
|
+
".cjs",
|
|
716
|
+
".json"
|
|
717
|
+
];
|
|
718
|
+
let hooksRegistered = false;
|
|
719
|
+
function ensureDebugImportHooks() {
|
|
720
|
+
if (hooksRegistered) return;
|
|
721
|
+
hooksRegistered = true;
|
|
722
|
+
const workspaceRoot = findWorkspaceRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
723
|
+
registerHooks({
|
|
724
|
+
resolve(specifier, context, nextResolve) {
|
|
725
|
+
const resolved = resolveSpecifier({
|
|
726
|
+
specifier,
|
|
727
|
+
parentURL: context.parentURL,
|
|
728
|
+
workspaceRoot
|
|
729
|
+
});
|
|
730
|
+
if (resolved) return {
|
|
731
|
+
url: resolved,
|
|
732
|
+
shortCircuit: true
|
|
733
|
+
};
|
|
734
|
+
return nextResolve(specifier, context);
|
|
735
|
+
},
|
|
736
|
+
load(url, context, nextLoad) {
|
|
737
|
+
const parsed = new URL(url);
|
|
738
|
+
const ext = path.extname(parsed.pathname);
|
|
739
|
+
if (SOURCE_EXTENSIONS.includes(ext)) return {
|
|
740
|
+
format: "module",
|
|
741
|
+
shortCircuit: true,
|
|
742
|
+
source: stripTypeScriptTypes(fs$1.readFileSync(fileURLToPath(parsed), "utf-8"), {
|
|
743
|
+
mode: "transform",
|
|
744
|
+
sourceMap: false
|
|
745
|
+
})
|
|
746
|
+
};
|
|
747
|
+
return nextLoad(url, context);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
function resolveSpecifier({ specifier, parentURL, workspaceRoot }) {
|
|
752
|
+
if (specifier.startsWith("node:")) return null;
|
|
753
|
+
if (specifier.startsWith("file:")) return toFileURL(resolveExistingPath(fileURLToPath(new URL(specifier))));
|
|
754
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
755
|
+
if (!parentURL?.startsWith("file:")) return null;
|
|
756
|
+
const parentPath = fileURLToPath(new URL(parentURL));
|
|
757
|
+
return toFileURL(resolveExistingPath(path.resolve(path.dirname(parentPath), specifier)));
|
|
758
|
+
}
|
|
759
|
+
if (path.isAbsolute(specifier)) return toFileURL(resolveExistingPath(specifier));
|
|
760
|
+
if (!workspaceRoot) return null;
|
|
761
|
+
return toFileURL(resolveWorkspaceAlias(specifier, workspaceRoot));
|
|
762
|
+
}
|
|
763
|
+
function resolveWorkspaceAlias(specifier, workspaceRoot) {
|
|
764
|
+
const exactAliasMap = new Map([
|
|
765
|
+
["@fastgpt-plugin/sdk-factory", path.join(workspaceRoot, "sdk/factory/src/index.ts")],
|
|
766
|
+
["@fastgpt-plugin/sdk-client", path.join(workspaceRoot, "sdk/client/src/index.ts")],
|
|
767
|
+
["sdk/factory/src", path.join(workspaceRoot, "sdk/factory/src/index.ts")],
|
|
768
|
+
["sdk/factory/dist", path.join(workspaceRoot, "sdk/factory/src/index.ts")],
|
|
769
|
+
["sdk/client/src", path.join(workspaceRoot, "sdk/client/src/index.ts")],
|
|
770
|
+
["sdk/client/dist", path.join(workspaceRoot, "sdk/client/src/index.ts")]
|
|
771
|
+
]);
|
|
772
|
+
const prefixAliasMap = [
|
|
773
|
+
["@fastgpt-plugin/sdk-factory/", path.join(workspaceRoot, "sdk/factory/src")],
|
|
774
|
+
["@fastgpt-plugin/sdk-client/", path.join(workspaceRoot, "sdk/client/src")],
|
|
775
|
+
["@domain/", path.join(workspaceRoot, "packages/domain/src")],
|
|
776
|
+
["@usecase/", path.join(workspaceRoot, "packages/usecase/src")],
|
|
777
|
+
["@shared/", path.join(workspaceRoot, "packages/shared/src")],
|
|
778
|
+
["@interface-adapter/", path.join(workspaceRoot, "packages/interface-adapter/src")],
|
|
779
|
+
["@infrastructure/", path.join(workspaceRoot, "packages/infrastructure/src")],
|
|
780
|
+
["@fastgpt-plugin/cli/", path.join(workspaceRoot, "apps/cli/src")],
|
|
781
|
+
["sdk/factory/src/", path.join(workspaceRoot, "sdk/factory/src")],
|
|
782
|
+
["sdk/factory/dist/", path.join(workspaceRoot, "sdk/factory/src")],
|
|
783
|
+
["sdk/client/src/", path.join(workspaceRoot, "sdk/client/src")],
|
|
784
|
+
["sdk/client/dist/", path.join(workspaceRoot, "sdk/client/src")]
|
|
785
|
+
];
|
|
786
|
+
const exact = exactAliasMap.get(specifier);
|
|
787
|
+
if (exact) return resolveExistingPath(exact);
|
|
788
|
+
for (const [prefix, targetDir] of prefixAliasMap) {
|
|
789
|
+
if (!specifier.startsWith(prefix)) continue;
|
|
790
|
+
const subPath = specifier.slice(prefix.length);
|
|
791
|
+
return resolveExistingPath(path.join(targetDir, subPath));
|
|
792
|
+
}
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
function resolveExistingPath(targetPath) {
|
|
796
|
+
const normalizedTarget = path.normalize(targetPath);
|
|
797
|
+
if (fs$1.existsSync(normalizedTarget) && fs$1.statSync(normalizedTarget).isFile()) return normalizedTarget;
|
|
798
|
+
for (const ext of RESOLVE_EXTENSIONS) {
|
|
799
|
+
const candidate = ext ? `${normalizedTarget}${ext}` : normalizedTarget;
|
|
800
|
+
if (fs$1.existsSync(candidate) && fs$1.statSync(candidate).isFile()) return candidate;
|
|
801
|
+
}
|
|
802
|
+
if (fs$1.existsSync(normalizedTarget) && fs$1.statSync(normalizedTarget).isDirectory()) for (const ext of RESOLVE_EXTENSIONS.filter(Boolean)) {
|
|
803
|
+
const candidate = path.join(normalizedTarget, `index${ext}`);
|
|
804
|
+
if (fs$1.existsSync(candidate) && fs$1.statSync(candidate).isFile()) return candidate;
|
|
805
|
+
}
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
function toFileURL(targetPath) {
|
|
809
|
+
if (!targetPath) return null;
|
|
810
|
+
return pathToFileURL(targetPath).href;
|
|
811
|
+
}
|
|
812
|
+
function findWorkspaceRoot(startDir) {
|
|
813
|
+
let currentDir = startDir;
|
|
814
|
+
while (true) {
|
|
815
|
+
const sdkFactoryIndex = path.join(currentDir, "sdk/factory/src/index.ts");
|
|
816
|
+
const domainDir = path.join(currentDir, "packages/domain/src");
|
|
817
|
+
if (fs$1.existsSync(sdkFactoryIndex) && fs$1.existsSync(domainDir)) return currentDir;
|
|
818
|
+
const parentDir = path.dirname(currentDir);
|
|
819
|
+
if (parentDir === currentDir) return;
|
|
820
|
+
currentDir = parentDir;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region ../../packages/domain/src/value-objects/file/MIME.vo.ts
|
|
826
|
+
const CommonMIMETypes = [
|
|
827
|
+
"application/javascript",
|
|
828
|
+
"application/json",
|
|
829
|
+
"application/yaml",
|
|
830
|
+
"application/zip",
|
|
831
|
+
"image/jpeg",
|
|
832
|
+
"image/png",
|
|
833
|
+
"image/gif",
|
|
834
|
+
"image/webp",
|
|
835
|
+
"image/svg+xml",
|
|
836
|
+
"application/pdf",
|
|
837
|
+
"text/plain",
|
|
838
|
+
"text/csv",
|
|
839
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
840
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
841
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
842
|
+
"application/msword",
|
|
843
|
+
"application/vnd.ms-excel",
|
|
844
|
+
"application/vnd.ms-powerpoint",
|
|
845
|
+
"text/markdown",
|
|
846
|
+
"audio/mpeg",
|
|
847
|
+
"video/mp4",
|
|
848
|
+
"audio/wav",
|
|
849
|
+
"text/html",
|
|
850
|
+
"application/xml",
|
|
851
|
+
"application/gzip",
|
|
852
|
+
"application/octet-stream",
|
|
853
|
+
"multipart/form-data",
|
|
854
|
+
"text/event-stream"
|
|
855
|
+
];
|
|
856
|
+
const MIMESchema = z.enum(CommonMIMETypes).and(z.string().regex(/^[^/]+\/[^/]+$/));
|
|
857
|
+
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region ../../packages/domain/src/value-objects/file/file.vo.ts
|
|
860
|
+
const FileMetaSchema = z.object({
|
|
861
|
+
fileKey: z.string(),
|
|
862
|
+
fileName: z.string(),
|
|
863
|
+
contentType: MIMESchema,
|
|
864
|
+
size: z.number(),
|
|
865
|
+
etag: z.string(),
|
|
866
|
+
createTime: z.date()
|
|
867
|
+
});
|
|
868
|
+
const FileCreateSchema = z.object({
|
|
869
|
+
/** 如果未提供 fileKey,则生成一个随机值 */
|
|
870
|
+
fileKey: z.string().optional(),
|
|
871
|
+
/** 路径,将会拼接在 fileName 之前 */
|
|
872
|
+
path: z.string().optional(),
|
|
873
|
+
/** 如果未提供fileName,则默认设置为 fileKey 的值 */
|
|
874
|
+
fileName: z.string().optional(),
|
|
875
|
+
/** 如果未提供 contentType,则尝试进行探测,探测失败则设置为 application/octet-stream */
|
|
876
|
+
contentType: MIMESchema.optional(),
|
|
877
|
+
overwrite: z.boolean().optional(),
|
|
878
|
+
file: z.union([z.instanceof(Readable, { error: "Stream cannot be empty" }), z.union([z.instanceof(Buffer, { error: "Buffer is required" }), z.instanceof(Uint8Array, { error: "Uint8Array is required" })]).transform((data) => {
|
|
879
|
+
if (data instanceof Uint8Array && !(data instanceof Buffer)) return Buffer.from(data);
|
|
880
|
+
return data;
|
|
881
|
+
})])
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region ../../packages/domain/src/ports/invoke.port.ts
|
|
886
|
+
const InvokeUploadFileInputSchema = z.object({ ...FileCreateSchema.omit({
|
|
887
|
+
fileKey: true,
|
|
888
|
+
overwrite: true,
|
|
889
|
+
path: true
|
|
890
|
+
}).shape });
|
|
891
|
+
const InvokeUploadFileOutputSchema = z.object({ accessURL: z.string() });
|
|
892
|
+
const InvokeUserInfoOutputSchema = z.object({
|
|
893
|
+
username: z.string(),
|
|
894
|
+
contact: z.string().nullish(),
|
|
895
|
+
memberName: z.string().nullish(),
|
|
896
|
+
orgs: z.array(z.object({
|
|
897
|
+
pathId: z.string(),
|
|
898
|
+
name: z.string()
|
|
899
|
+
})),
|
|
900
|
+
groups: z.array(z.object({ name: z.string() }))
|
|
901
|
+
});
|
|
902
|
+
const InvokeMethodEnumSchema = z.enum(["uploadFile", "userInfo"]);
|
|
903
|
+
const InvokeMethodEnum = InvokeMethodEnumSchema.enum;
|
|
904
|
+
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region ../../packages/domain/src/value-objects/stream.vo.ts
|
|
907
|
+
var StreamData = class StreamData {
|
|
908
|
+
stream;
|
|
909
|
+
closed = false;
|
|
910
|
+
constructor(initializer) {
|
|
911
|
+
this.stream = new Readable$1({
|
|
912
|
+
objectMode: true,
|
|
913
|
+
read() {}
|
|
914
|
+
});
|
|
915
|
+
initializer?.(this);
|
|
916
|
+
}
|
|
917
|
+
static create(initializer) {
|
|
918
|
+
return new StreamData(initializer);
|
|
919
|
+
}
|
|
920
|
+
write(data) {
|
|
921
|
+
this.ensureWritable();
|
|
922
|
+
this.stream.push(data);
|
|
923
|
+
}
|
|
924
|
+
send(data) {
|
|
925
|
+
this.write(data);
|
|
926
|
+
}
|
|
927
|
+
close() {
|
|
928
|
+
if (this.closed) return;
|
|
929
|
+
this.closed = true;
|
|
930
|
+
this.stream.push(null);
|
|
931
|
+
}
|
|
932
|
+
end() {
|
|
933
|
+
this.close();
|
|
934
|
+
}
|
|
935
|
+
fail(error) {
|
|
936
|
+
this.closed = true;
|
|
937
|
+
this.stream.destroy(error);
|
|
938
|
+
}
|
|
939
|
+
toReadable() {
|
|
940
|
+
return this.stream;
|
|
941
|
+
}
|
|
942
|
+
onData(listener) {
|
|
943
|
+
this.stream.on("data", listener);
|
|
944
|
+
return this;
|
|
945
|
+
}
|
|
946
|
+
onEnd(listener) {
|
|
947
|
+
this.stream.on("end", listener);
|
|
948
|
+
return this;
|
|
949
|
+
}
|
|
950
|
+
onError(listener) {
|
|
951
|
+
this.stream.on("error", listener);
|
|
952
|
+
return this;
|
|
953
|
+
}
|
|
954
|
+
async consume(consumer) {
|
|
955
|
+
for await (const chunk of this.values()) await consumer(chunk);
|
|
956
|
+
}
|
|
957
|
+
async *values() {
|
|
958
|
+
for await (const chunk of this.stream) yield chunk;
|
|
959
|
+
}
|
|
960
|
+
ensureWritable() {
|
|
961
|
+
if (this.closed || this.stream.destroyed) throw new Error("StreamData is already closed");
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region ../../packages/domain/src/value-objects/system-var.vo.ts
|
|
967
|
+
const SystemVarSchema = z.object({
|
|
968
|
+
app: z.object({
|
|
969
|
+
id: z.string(),
|
|
970
|
+
name: z.string()
|
|
971
|
+
}),
|
|
972
|
+
chat: z.object({
|
|
973
|
+
chatId: z.string(),
|
|
974
|
+
uid: z.string().optional()
|
|
975
|
+
}),
|
|
976
|
+
invokeToken: z.string(),
|
|
977
|
+
time: z.string()
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region ../../packages/domain/src/value-objects/tool.vo.ts
|
|
982
|
+
const ToolHandlerReturnSchema = z.record(z.string(), z.unknown());
|
|
983
|
+
const StreamMessageTypeSchema = z.enum([
|
|
984
|
+
"response",
|
|
985
|
+
"error",
|
|
986
|
+
"stream"
|
|
987
|
+
]);
|
|
988
|
+
const StreamMessageTypeEnum = StreamMessageTypeSchema.enum;
|
|
989
|
+
const StreamDataAnswerTypeSchema = z.enum(["answer", "fastAnswer"]);
|
|
990
|
+
const StreamDataAnswerTypeEnum = StreamDataAnswerTypeSchema.enum;
|
|
991
|
+
const ToolAnswerSchema = z.object({
|
|
992
|
+
type: StreamDataAnswerTypeSchema,
|
|
993
|
+
content: z.string()
|
|
994
|
+
});
|
|
995
|
+
const ToolStreamMessageSchema = z.discriminatedUnion("type", [
|
|
996
|
+
z.object({
|
|
997
|
+
type: z.literal(StreamMessageTypeEnum.response),
|
|
998
|
+
data: ToolHandlerReturnSchema
|
|
999
|
+
}),
|
|
1000
|
+
z.object({
|
|
1001
|
+
type: z.literal(StreamMessageTypeEnum.stream),
|
|
1002
|
+
data: ToolAnswerSchema
|
|
1003
|
+
}),
|
|
1004
|
+
z.object({
|
|
1005
|
+
type: z.literal(StreamMessageTypeEnum.error),
|
|
1006
|
+
data: z.string()
|
|
1007
|
+
})
|
|
1008
|
+
]);
|
|
1009
|
+
const ToolRunInputSchema = z.object({
|
|
1010
|
+
pluginId: z.string(),
|
|
1011
|
+
version: z.preprocess((value) => {
|
|
1012
|
+
if (value === "") return void 0;
|
|
1013
|
+
return value;
|
|
1014
|
+
}, z.string().optional()),
|
|
1015
|
+
source: PluginSourceSchema.optional(),
|
|
1016
|
+
childId: z.string().optional(),
|
|
1017
|
+
input: z.record(z.string(), z.unknown()),
|
|
1018
|
+
secrets: z.record(z.string(), z.unknown()).optional(),
|
|
1019
|
+
systemVar: SystemVarSchema
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
//#endregion
|
|
1023
|
+
//#region ../../packages/infrastructure/src/plugin/plugin-runtime/drivers/local-pool/ipc-channel.ts
|
|
1024
|
+
const HOST_INVOKE_METHOD = "__host_invoke__";
|
|
1025
|
+
|
|
1026
|
+
//#endregion
|
|
1027
|
+
//#region src/debug/runtime.ts
|
|
1028
|
+
const LOCAL_DEBUG_RUNTIME_GLOBAL_KEY = "__FASTGPT_PLUGIN_LOCAL_DEBUG_RUNTIME__";
|
|
1029
|
+
const LOCAL_DEBUG_DUPLEX_REPLY_MARK = "__localDebugDuplexReply__";
|
|
1030
|
+
var LocalDebugChannel = class {
|
|
1031
|
+
constructor(runtime) {
|
|
1032
|
+
this.runtime = runtime;
|
|
1033
|
+
}
|
|
1034
|
+
setRequestHandler(handler) {
|
|
1035
|
+
this.runtime.setPluginRequestHandler(handler);
|
|
1036
|
+
}
|
|
1037
|
+
async requestDuplex(method, params, options) {
|
|
1038
|
+
return this.runtime.invokeHost(method, params, {
|
|
1039
|
+
...options,
|
|
1040
|
+
streamName: method
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
replyDuplex(_message, result, options) {
|
|
1044
|
+
return {
|
|
1045
|
+
[LOCAL_DEBUG_DUPLEX_REPLY_MARK]: true,
|
|
1046
|
+
...result !== void 0 ? { result } : {},
|
|
1047
|
+
...options?.output !== void 0 ? { output: options.output } : {},
|
|
1048
|
+
...options ? { options: {
|
|
1049
|
+
traceId: options.traceId,
|
|
1050
|
+
outputMeta: options.outputMeta,
|
|
1051
|
+
outputStreamId: options.outputStreamId
|
|
1052
|
+
} } : {}
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
sendReady() {
|
|
1056
|
+
this.runtime.markReady();
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
var LocalDebugRuntime = class {
|
|
1060
|
+
pluginRequestHandler = null;
|
|
1061
|
+
hostRequestHandler = null;
|
|
1062
|
+
ready = false;
|
|
1063
|
+
readyPromise;
|
|
1064
|
+
resolveReady;
|
|
1065
|
+
pluginChannel;
|
|
1066
|
+
constructor() {
|
|
1067
|
+
this.pluginChannel = new LocalDebugChannel(this);
|
|
1068
|
+
this.readyPromise = new Promise((resolve) => {
|
|
1069
|
+
this.resolveReady = resolve;
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
setPluginRequestHandler(handler) {
|
|
1073
|
+
this.pluginRequestHandler = handler;
|
|
1074
|
+
}
|
|
1075
|
+
setHostRequestHandler(handler) {
|
|
1076
|
+
this.hostRequestHandler = handler;
|
|
1077
|
+
}
|
|
1078
|
+
markReady() {
|
|
1079
|
+
if (this.ready) return;
|
|
1080
|
+
this.ready = true;
|
|
1081
|
+
this.resolveReady();
|
|
1082
|
+
}
|
|
1083
|
+
async waitUntilReady() {
|
|
1084
|
+
await this.readyPromise;
|
|
1085
|
+
}
|
|
1086
|
+
async invokePlugin(method, params, options) {
|
|
1087
|
+
if (!this.pluginRequestHandler) throw new Error("Plugin request handler is not initialized.");
|
|
1088
|
+
return this.dispatch(this.pluginRequestHandler, method, params, {
|
|
1089
|
+
...options,
|
|
1090
|
+
streamName: method
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
async invokeHost(method, params, options) {
|
|
1094
|
+
if (!this.hostRequestHandler) throw new Error(`Local debug host handler is not configured: ${method}`);
|
|
1095
|
+
const message = createMessage(method, params, options);
|
|
1096
|
+
return toDuplexResponse(message, await this.hostRequestHandler({
|
|
1097
|
+
message,
|
|
1098
|
+
method,
|
|
1099
|
+
args: params,
|
|
1100
|
+
traceId: options.traceId,
|
|
1101
|
+
input: options.input,
|
|
1102
|
+
replyDuplex: (result, replyOptions) => this.pluginChannel.replyDuplex(message, result, replyOptions)
|
|
1103
|
+
}), options);
|
|
1104
|
+
}
|
|
1105
|
+
async dispatch(handler, method, params, options) {
|
|
1106
|
+
const message = createMessage(method, params, options);
|
|
1107
|
+
return toDuplexResponse(message, await handler(message), options);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
const createLocalDebugRuntime = () => new LocalDebugRuntime();
|
|
1111
|
+
const setCurrentLocalDebugRuntime = (runtime) => {
|
|
1112
|
+
const store = globalThis;
|
|
1113
|
+
if (!runtime) {
|
|
1114
|
+
delete store[LOCAL_DEBUG_RUNTIME_GLOBAL_KEY];
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
store[LOCAL_DEBUG_RUNTIME_GLOBAL_KEY] = runtime;
|
|
1118
|
+
};
|
|
1119
|
+
function createMessage(method, params, options) {
|
|
1120
|
+
return {
|
|
1121
|
+
id: options?.requestId ?? randomUUID(),
|
|
1122
|
+
messageType: "request",
|
|
1123
|
+
method,
|
|
1124
|
+
params,
|
|
1125
|
+
traceId: options?.traceId,
|
|
1126
|
+
timestamp: Date.now()
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function isLocalDebugReplyDescriptor(value) {
|
|
1130
|
+
return Boolean(value && typeof value === "object" && LOCAL_DEBUG_DUPLEX_REPLY_MARK in value && value[LOCAL_DEBUG_DUPLEX_REPLY_MARK] === true);
|
|
1131
|
+
}
|
|
1132
|
+
function toDuplexResponse(message, response, options) {
|
|
1133
|
+
const inputDone = options?.input !== void 0 ? Promise.resolve() : void 0;
|
|
1134
|
+
if (!isLocalDebugReplyDescriptor(response)) return {
|
|
1135
|
+
requestId: message.id,
|
|
1136
|
+
result: response,
|
|
1137
|
+
...inputDone ? { inputDone } : {}
|
|
1138
|
+
};
|
|
1139
|
+
const output = response.output !== void 0 ? createIncomingStream({
|
|
1140
|
+
source: response.output,
|
|
1141
|
+
streamName: response.options?.outputStreamId ?? options?.streamName ?? message.method ?? "stream",
|
|
1142
|
+
streamId: response.options?.outputStreamId ?? randomUUID(),
|
|
1143
|
+
traceId: response.options?.traceId ?? message.traceId,
|
|
1144
|
+
meta: response.options?.outputMeta
|
|
1145
|
+
}) : void 0;
|
|
1146
|
+
return {
|
|
1147
|
+
requestId: message.id,
|
|
1148
|
+
result: response.result,
|
|
1149
|
+
...output ? { output } : {},
|
|
1150
|
+
...inputDone ? { inputDone } : {}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function createIncomingStream({ source, streamName, streamId, traceId, meta }) {
|
|
1154
|
+
return {
|
|
1155
|
+
streamId,
|
|
1156
|
+
streamName,
|
|
1157
|
+
stream: toStreamData(source),
|
|
1158
|
+
...traceId ? { traceId } : {},
|
|
1159
|
+
...meta !== void 0 ? { meta } : {}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
function toStreamData(source) {
|
|
1163
|
+
if (source instanceof StreamData) return source;
|
|
1164
|
+
const output = StreamData.create();
|
|
1165
|
+
(async () => {
|
|
1166
|
+
try {
|
|
1167
|
+
for await (const chunk of source) output.send(chunk);
|
|
1168
|
+
output.end();
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
output.fail(error instanceof Error ? error : new Error(String(error)));
|
|
1171
|
+
}
|
|
1172
|
+
})();
|
|
1173
|
+
return output;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region src/debug/session.ts
|
|
1178
|
+
async function loadDebugSession({ entryDir, uploadDir }) {
|
|
1179
|
+
const resolvedEntryDir = path.resolve(entryDir);
|
|
1180
|
+
const indexPath = path.join(resolvedEntryDir, "index.ts");
|
|
1181
|
+
await assertPathExists(resolvedEntryDir, `调试目录不存在: ${resolvedEntryDir}`);
|
|
1182
|
+
await assertPathExists(indexPath, `找不到 index.ts: ${indexPath}`);
|
|
1183
|
+
ensureDebugImportHooks();
|
|
1184
|
+
const runtime = createLocalDebugRuntime();
|
|
1185
|
+
runtime.setHostRequestHandler(createVirtualHostHandler(uploadDir));
|
|
1186
|
+
const previousRuntimeMode = process.env.RUNTIME_MODE;
|
|
1187
|
+
process.env.RUNTIME_MODE = "dev";
|
|
1188
|
+
setCurrentLocalDebugRuntime(runtime);
|
|
1189
|
+
try {
|
|
1190
|
+
const tool = (await import(`${pathToFileURL(indexPath).href}#fastgpt-plugin-debug-${Date.now()}-${Math.random().toString(16).slice(2)}`)).default;
|
|
1191
|
+
if (!isPackableToolExport(tool)) throw new Error("index.ts 的 default export 必须是 defineTool()/defineToolSet() 返回的 factory-sdk 实例");
|
|
1192
|
+
await runtime.waitUntilReady();
|
|
1193
|
+
return {
|
|
1194
|
+
runtime,
|
|
1195
|
+
uploadDir: path.resolve(uploadDir),
|
|
1196
|
+
snapshot: createDebugSnapshot(tool, {
|
|
1197
|
+
entryDir: resolvedEntryDir,
|
|
1198
|
+
indexPath
|
|
1199
|
+
})
|
|
1200
|
+
};
|
|
1201
|
+
} finally {
|
|
1202
|
+
setCurrentLocalDebugRuntime(void 0);
|
|
1203
|
+
if (previousRuntimeMode === void 0) delete process.env.RUNTIME_MODE;
|
|
1204
|
+
else process.env.RUNTIME_MODE = previousRuntimeMode;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async function runDebugTool({ runtime, snapshot, toolId, input, secrets, systemVar }) {
|
|
1208
|
+
const targetTool = pickTargetTool(snapshot, toolId);
|
|
1209
|
+
const mergedSystemVar = createDebugSystemVar(snapshot, targetTool.id, systemVar);
|
|
1210
|
+
const response = await runtime.invokePlugin("run", {
|
|
1211
|
+
input,
|
|
1212
|
+
systemVar: mergedSystemVar,
|
|
1213
|
+
...snapshot.isToolSet ? { childId: targetTool.id } : {},
|
|
1214
|
+
...secrets ? { secrets } : {}
|
|
1215
|
+
}, { traceId: randomUUID() });
|
|
1216
|
+
if (!response.output) throw new Error("调试运行未返回输出流。");
|
|
1217
|
+
const streamMessages = [];
|
|
1218
|
+
let finalResponse;
|
|
1219
|
+
let finalError;
|
|
1220
|
+
await response.output.stream.consume(async (chunk) => {
|
|
1221
|
+
const parsed = ToolStreamMessageSchema.parse(chunk);
|
|
1222
|
+
streamMessages.push(parsed);
|
|
1223
|
+
if (parsed.type === "response") finalResponse = parsed.data;
|
|
1224
|
+
if (parsed.type === "error") finalError = parsed.data;
|
|
1225
|
+
});
|
|
1226
|
+
return {
|
|
1227
|
+
systemVar: mergedSystemVar,
|
|
1228
|
+
response: finalResponse,
|
|
1229
|
+
error: finalError,
|
|
1230
|
+
streamMessages
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function createDebugSystemVar(snapshot, toolId, overrides) {
|
|
1234
|
+
const base = {
|
|
1235
|
+
app: {
|
|
1236
|
+
id: "debug-app",
|
|
1237
|
+
name: "FastGPT Local Debug"
|
|
1238
|
+
},
|
|
1239
|
+
chat: {
|
|
1240
|
+
chatId: "debug-chat",
|
|
1241
|
+
uid: "debugger"
|
|
1242
|
+
},
|
|
1243
|
+
invokeToken: "debug-invoke-token",
|
|
1244
|
+
time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1245
|
+
};
|
|
1246
|
+
return SystemVarSchema.parse({
|
|
1247
|
+
...base,
|
|
1248
|
+
...overrides,
|
|
1249
|
+
app: {
|
|
1250
|
+
...base.app,
|
|
1251
|
+
...overrides?.app ?? {}
|
|
1252
|
+
},
|
|
1253
|
+
chat: {
|
|
1254
|
+
...base.chat,
|
|
1255
|
+
...overrides?.chat ?? {}
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
function createDebugSnapshot(tool, location) {
|
|
1260
|
+
const userManifest = tool.getUserToolManifest();
|
|
1261
|
+
const children = tool.getChildManifests();
|
|
1262
|
+
const secretSchema = z.toJSONSchema(tool.getSecretSchema());
|
|
1263
|
+
const tools = children.length === 0 ? [createSingleToolSnapshot(tool, userManifest)] : children.map((child) => createChildToolSnapshot(tool, child));
|
|
1264
|
+
return {
|
|
1265
|
+
entryDir: location.entryDir,
|
|
1266
|
+
indexPath: location.indexPath,
|
|
1267
|
+
pluginId: getRequiredString(userManifest.pluginId, "缺少 pluginId"),
|
|
1268
|
+
version: getRequiredString(userManifest.version, "缺少 version"),
|
|
1269
|
+
name: pickLocalizedText(userManifest.name),
|
|
1270
|
+
description: pickLocalizedText(userManifest.description),
|
|
1271
|
+
toolDescription: pickToolDescription(getOptionalString(userManifest.toolDescription), userManifest.description),
|
|
1272
|
+
author: getOptionalString(userManifest.author),
|
|
1273
|
+
tags: toStringArray(userManifest.tags),
|
|
1274
|
+
permissions: toStringArray(userManifest.permission),
|
|
1275
|
+
secretSchema: ensurePlainObject(secretSchema),
|
|
1276
|
+
isToolSet: children.length > 0,
|
|
1277
|
+
tools
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
function createSingleToolSnapshot(tool, userManifest) {
|
|
1281
|
+
const handler = tool.getToolHandler();
|
|
1282
|
+
if (!handler) throw new Error("default export 未注册任何工具 handler");
|
|
1283
|
+
return {
|
|
1284
|
+
id: "tool",
|
|
1285
|
+
name: pickLocalizedText(userManifest.name),
|
|
1286
|
+
description: pickLocalizedText(userManifest.description),
|
|
1287
|
+
toolDescription: pickToolDescription(getOptionalString(userManifest.toolDescription), userManifest.description),
|
|
1288
|
+
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1289
|
+
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function createChildToolSnapshot(tool, child) {
|
|
1293
|
+
const handler = tool.getToolHandler(child.id);
|
|
1294
|
+
if (!handler) throw new Error(`找不到子工具 handler: ${child.id}`);
|
|
1295
|
+
return {
|
|
1296
|
+
id: child.id,
|
|
1297
|
+
name: pickLocalizedText(child.name),
|
|
1298
|
+
description: pickLocalizedText(child.description),
|
|
1299
|
+
toolDescription: pickToolDescription(child.toolDescription, child.description),
|
|
1300
|
+
inputSchema: ensurePlainObject(z.toJSONSchema(handler.inputSchema)),
|
|
1301
|
+
outputSchema: ensurePlainObject(z.toJSONSchema(handler.outputSchema))
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
function pickTargetTool(snapshot, toolId) {
|
|
1305
|
+
if (!snapshot.isToolSet) return snapshot.tools[0];
|
|
1306
|
+
if (!toolId) throw new Error("当前插件是工具集,请通过 --tool <childId> 指定要调试的子工具。");
|
|
1307
|
+
const tool = snapshot.tools.find((item) => item.id === toolId);
|
|
1308
|
+
if (!tool) throw new Error(`找不到子工具: ${toolId}`);
|
|
1309
|
+
return tool;
|
|
1310
|
+
}
|
|
1311
|
+
function createVirtualHostHandler(uploadDir) {
|
|
1312
|
+
return async ({ method, args, input }) => {
|
|
1313
|
+
if (method !== "__host_invoke__") throw new Error(`本地虚拟环境暂不支持反向调用: ${method}`);
|
|
1314
|
+
const payload = ensurePlainObject(args);
|
|
1315
|
+
const invokeMethod = payload.method;
|
|
1316
|
+
const invokeArgs = ensurePlainObject(payload.args);
|
|
1317
|
+
switch (invokeMethod) {
|
|
1318
|
+
case InvokeMethodEnum.uploadFile: {
|
|
1319
|
+
const buffer = await readSourceToBuffer(input);
|
|
1320
|
+
const fileName = sanitizeFileName(typeof invokeArgs.fileName === "string" && invokeArgs.fileName.trim().length > 0 ? invokeArgs.fileName : "debug-upload.bin");
|
|
1321
|
+
const contentType = typeof invokeArgs.contentType === "string" && invokeArgs.contentType.trim().length > 0 ? invokeArgs.contentType : "application/octet-stream";
|
|
1322
|
+
const saveDir = path.resolve(uploadDir);
|
|
1323
|
+
const outputPath = path.join(saveDir, `${Date.now()}-${randomUUID().slice(0, 8)}-${fileName}`);
|
|
1324
|
+
await fs.mkdir(saveDir, { recursive: true });
|
|
1325
|
+
await fs.writeFile(outputPath, buffer);
|
|
1326
|
+
return {
|
|
1327
|
+
fileName,
|
|
1328
|
+
contentType,
|
|
1329
|
+
size: buffer.length,
|
|
1330
|
+
etag: createHash("md5").update(buffer).digest("hex"),
|
|
1331
|
+
createTime: /* @__PURE__ */ new Date(),
|
|
1332
|
+
accessURL: pathToFileURL(outputPath).href
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
default: throw new Error(`本地虚拟环境暂不支持反向调用: ${String(invokeMethod)}`);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
async function readSourceToBuffer(source) {
|
|
1340
|
+
if (!source) return Buffer.alloc(0);
|
|
1341
|
+
const chunks = [];
|
|
1342
|
+
const iterable = source instanceof StreamData ? source.values() : source;
|
|
1343
|
+
for await (const chunk of iterable) chunks.push(toBuffer(chunk));
|
|
1344
|
+
return Buffer.concat(chunks);
|
|
1345
|
+
}
|
|
1346
|
+
function toBuffer(value) {
|
|
1347
|
+
if (Buffer.isBuffer(value)) return value;
|
|
1348
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
1349
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
1350
|
+
return Buffer.from(JSON.stringify(value));
|
|
1351
|
+
}
|
|
1352
|
+
function isPackableToolExport(value) {
|
|
1353
|
+
return Boolean(value && typeof value === "object" && "getUserToolManifest" in value && typeof value.getUserToolManifest === "function" && "getSecretSchema" in value && typeof value.getSecretSchema === "function" && "getToolHandler" in value && typeof value.getToolHandler === "function" && "getChildManifests" in value && typeof value.getChildManifests === "function");
|
|
1354
|
+
}
|
|
1355
|
+
function pickLocalizedText(value) {
|
|
1356
|
+
if (typeof value === "string") return value;
|
|
1357
|
+
if (value && typeof value === "object") {
|
|
1358
|
+
const localized = value;
|
|
1359
|
+
const preferred = [
|
|
1360
|
+
localized["zh-CN"],
|
|
1361
|
+
localized.en,
|
|
1362
|
+
...Object.values(localized)
|
|
1363
|
+
].find((item) => typeof item === "string" && item.trim().length > 0);
|
|
1364
|
+
if (typeof preferred === "string") return preferred;
|
|
1365
|
+
}
|
|
1366
|
+
return "";
|
|
1367
|
+
}
|
|
1368
|
+
function pickToolDescription(explicitDescription, fallbackSource) {
|
|
1369
|
+
if (explicitDescription && explicitDescription.trim().length > 0) return explicitDescription;
|
|
1370
|
+
return pickLocalizedText(fallbackSource);
|
|
1371
|
+
}
|
|
1372
|
+
function getRequiredString(value, message) {
|
|
1373
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
1374
|
+
throw new Error(message);
|
|
1375
|
+
}
|
|
1376
|
+
function getOptionalString(value) {
|
|
1377
|
+
return typeof value === "string" ? value : void 0;
|
|
1378
|
+
}
|
|
1379
|
+
function toStringArray(value) {
|
|
1380
|
+
if (!Array.isArray(value)) return;
|
|
1381
|
+
const items = value.filter((item) => typeof item === "string");
|
|
1382
|
+
return items.length > 0 ? items : void 0;
|
|
1383
|
+
}
|
|
1384
|
+
function ensurePlainObject(value) {
|
|
1385
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1386
|
+
}
|
|
1387
|
+
function sanitizeFileName(fileName) {
|
|
1388
|
+
return Array.from(fileName, (char) => {
|
|
1389
|
+
const code = char.charCodeAt(0);
|
|
1390
|
+
if (INVALID_FILE_NAME_CHARS.has(char) || code <= 31) return "_";
|
|
1391
|
+
return char;
|
|
1392
|
+
}).join("");
|
|
1393
|
+
}
|
|
1394
|
+
const INVALID_FILE_NAME_CHARS = new Set([
|
|
1395
|
+
"<",
|
|
1396
|
+
">",
|
|
1397
|
+
":",
|
|
1398
|
+
"\"",
|
|
1399
|
+
"/",
|
|
1400
|
+
"\\",
|
|
1401
|
+
"|",
|
|
1402
|
+
"?",
|
|
1403
|
+
"*"
|
|
1404
|
+
]);
|
|
1405
|
+
async function assertPathExists(targetPath, message) {
|
|
1406
|
+
try {
|
|
1407
|
+
await fs.access(targetPath);
|
|
1408
|
+
} catch {
|
|
1409
|
+
throw new Error(message);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
//#endregion
|
|
1414
|
+
//#region src/commands/debug.ts
|
|
1415
|
+
var DebugCommand = class extends BaseCommand {
|
|
1416
|
+
register(parent) {
|
|
1417
|
+
parent.command("debug <entry>").description("直接加载本地插件目录并调试工具").option("-t, --tool <childId>", "工具集子工具 ID").option("--run", "立即执行一次工具调试", false).option("-i, --input <json>", "工具输入 JSON 字符串").option("--input-file <path>", "工具输入 JSON 文件路径").option("--secrets <json>", "secrets JSON 字符串").option("--secrets-file <path>", "secrets JSON 文件路径").option("--system-var <json>", "systemVar JSON 字符串").option("--system-var-file <path>", "systemVar JSON 文件路径").option("--upload-dir <path>", "虚拟 uploadFile 的输出目录").action(async (entry, opts) => {
|
|
1418
|
+
await this.run(entry, opts);
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
async run(entry, options) {
|
|
1422
|
+
const entryDir = path.resolve(entry);
|
|
1423
|
+
const uploadDir = path.resolve(options.uploadDir ?? path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
|
|
1424
|
+
const session = await loadDebugSession({
|
|
1425
|
+
entryDir,
|
|
1426
|
+
uploadDir
|
|
1427
|
+
});
|
|
1428
|
+
this.printSnapshot(session.snapshot, uploadDir);
|
|
1429
|
+
if (!options.run) return;
|
|
1430
|
+
const input = await this.readJsonOption(options.input, options.inputFile, "input");
|
|
1431
|
+
const secrets = await this.readJsonOption(options.secrets, options.secretsFile, "secrets");
|
|
1432
|
+
const systemVar = await this.readJsonOption(options.systemVar, options.systemVarFile, "systemVar");
|
|
1433
|
+
const result = await runDebugTool({
|
|
1434
|
+
runtime: session.runtime,
|
|
1435
|
+
snapshot: session.snapshot,
|
|
1436
|
+
toolId: options.tool,
|
|
1437
|
+
input: input ?? {},
|
|
1438
|
+
secrets,
|
|
1439
|
+
systemVar
|
|
1440
|
+
});
|
|
1441
|
+
logger.success(`本地调试执行完成: ${session.snapshot.isToolSet ? options.tool ?? "" : session.snapshot.pluginId}`.trim());
|
|
1442
|
+
logger.info(`虚拟时间: ${result.systemVar.time}`);
|
|
1443
|
+
this.printStreamMessages(result.streamMessages);
|
|
1444
|
+
if (result.response) logger.info(`返回结果:\n${JSON.stringify(result.response, null, 2)}`);
|
|
1445
|
+
if (result.error) {
|
|
1446
|
+
logger.error(result.error);
|
|
1447
|
+
process.exit(1);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
printSnapshot(snapshot, uploadDir) {
|
|
1451
|
+
const lines = [
|
|
1452
|
+
"插件信息",
|
|
1453
|
+
` entry: ${snapshot.entryDir}`,
|
|
1454
|
+
` index: ${snapshot.indexPath}`,
|
|
1455
|
+
` pluginId: ${snapshot.pluginId}`,
|
|
1456
|
+
` version: ${snapshot.version}`,
|
|
1457
|
+
` name: ${snapshot.name || "-"}`,
|
|
1458
|
+
` description: ${snapshot.description || "-"}`,
|
|
1459
|
+
` toolDescription: ${snapshot.toolDescription || "-"}`,
|
|
1460
|
+
` author: ${snapshot.author ?? "-"}`,
|
|
1461
|
+
` tags: ${snapshot.tags?.join(", ") ?? "-"}`,
|
|
1462
|
+
` permissions: ${snapshot.permissions?.join(", ") ?? "-"}`,
|
|
1463
|
+
` uploadDir: ${uploadDir}`,
|
|
1464
|
+
` secretKeys: ${this.formatSchemaKeys(snapshot.secretSchema)}`
|
|
1465
|
+
];
|
|
1466
|
+
logger.info(lines.join("\n"));
|
|
1467
|
+
const toolLines = ["可调试工具"];
|
|
1468
|
+
snapshot.tools.forEach((tool) => {
|
|
1469
|
+
toolLines.push(` - id: ${tool.id}`);
|
|
1470
|
+
toolLines.push(` name: ${tool.name || "-"}`);
|
|
1471
|
+
toolLines.push(` description: ${tool.description || "-"}`);
|
|
1472
|
+
toolLines.push(` inputKeys: ${this.formatSchemaKeys(tool.inputSchema)}`);
|
|
1473
|
+
toolLines.push(` command: ${this.buildCommand(snapshot, tool)}`);
|
|
1474
|
+
});
|
|
1475
|
+
logger.info(toolLines.join("\n"));
|
|
1476
|
+
}
|
|
1477
|
+
printStreamMessages(messages) {
|
|
1478
|
+
if (messages.length === 0) {
|
|
1479
|
+
logger.info("流式输出: 无");
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const lines = ["流式输出"];
|
|
1483
|
+
messages.forEach((message) => {
|
|
1484
|
+
switch (message.type) {
|
|
1485
|
+
case "stream":
|
|
1486
|
+
lines.push(` [${message.data.type}] ${message.data.content}`);
|
|
1487
|
+
break;
|
|
1488
|
+
case "response":
|
|
1489
|
+
lines.push(" [response] 已收到最终返回结果");
|
|
1490
|
+
break;
|
|
1491
|
+
case "error":
|
|
1492
|
+
lines.push(` [error] ${message.data}`);
|
|
1493
|
+
break;
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
logger.info(lines.join("\n"));
|
|
1497
|
+
}
|
|
1498
|
+
buildCommand(snapshot, tool) {
|
|
1499
|
+
return [
|
|
1500
|
+
"fastgpt-plugin",
|
|
1501
|
+
"debug",
|
|
1502
|
+
snapshot.entryDir,
|
|
1503
|
+
"--run",
|
|
1504
|
+
...snapshot.isToolSet ? ["--tool", tool.id] : [],
|
|
1505
|
+
"--input",
|
|
1506
|
+
JSON.stringify(this.createInputExample(tool.inputSchema))
|
|
1507
|
+
].map(quoteShellArg).join(" ");
|
|
1508
|
+
}
|
|
1509
|
+
createInputExample(schema) {
|
|
1510
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
1511
|
+
if ("const" in schema) return schema.const;
|
|
1512
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return this.createInputExample(asObject(schema.anyOf[0]));
|
|
1513
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return this.createInputExample(asObject(schema.oneOf[0]));
|
|
1514
|
+
switch (schema.type) {
|
|
1515
|
+
case "object": {
|
|
1516
|
+
const properties = asObject(schema.properties);
|
|
1517
|
+
return Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, this.createInputExample(asObject(value))]));
|
|
1518
|
+
}
|
|
1519
|
+
case "array": return [];
|
|
1520
|
+
case "integer":
|
|
1521
|
+
case "number": return 0;
|
|
1522
|
+
case "boolean": return true;
|
|
1523
|
+
case "string": return "";
|
|
1524
|
+
default: return {};
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
formatSchemaKeys(schema) {
|
|
1528
|
+
const properties = asObject(schema.properties);
|
|
1529
|
+
const keys = Object.keys(properties);
|
|
1530
|
+
return keys.length > 0 ? keys.join(", ") : "-";
|
|
1531
|
+
}
|
|
1532
|
+
async readJsonOption(inlineValue, filePath, label) {
|
|
1533
|
+
if (!inlineValue && !filePath) return;
|
|
1534
|
+
if (inlineValue && filePath) throw new Error(`${label} 只能传入一种来源:JSON 字符串或 JSON 文件。`);
|
|
1535
|
+
const raw = inlineValue ?? await fs.readFile(path.resolve(filePath), "utf-8");
|
|
1536
|
+
try {
|
|
1537
|
+
const parsed = JSON.parse(raw);
|
|
1538
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
1539
|
+
return parsed;
|
|
1540
|
+
} catch {
|
|
1541
|
+
throw new Error(`${label} 解析失败,请提供合法的 JSON object。`);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
function asObject(value) {
|
|
1546
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1547
|
+
}
|
|
1548
|
+
function quoteShellArg(value) {
|
|
1549
|
+
if (/^[a-zA-Z0-9_./:@-]+$/.test(value)) return value;
|
|
1550
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
503
1553
|
//#endregion
|
|
504
1554
|
//#region src/commands/pack.ts
|
|
1555
|
+
/**
|
|
1556
|
+
* `.pkg` 文件结构:
|
|
1557
|
+
* index.js
|
|
1558
|
+
* manifest.json
|
|
1559
|
+
* *.logo.*
|
|
1560
|
+
* README.md(可选)
|
|
1561
|
+
* assets/**(可选)
|
|
1562
|
+
*/
|
|
505
1563
|
var PackCommand = class extends BaseCommand {
|
|
506
1564
|
register(parent) {
|
|
507
|
-
parent.command("pack").description("
|
|
1565
|
+
parent.command("pack").description("基于 build 产物打包 FastGPT 工具或工具集").option("-e, --entry <path>", "工具源码根目录", process.cwd()).option("-d, --dist <path>", "构建产物目录", "./dist").option("-o, --output <path>", "pkg 输出目录(默认使用入口目录)").option("-n, --name <name>", "包名称(默认使用入口目录名)").action(async (opts) => {
|
|
508
1566
|
await this.run(opts);
|
|
509
1567
|
});
|
|
510
1568
|
}
|
|
511
1569
|
async run(options) {
|
|
512
1570
|
const start = Date.now();
|
|
513
1571
|
const entryDir = path.resolve(options.entry);
|
|
1572
|
+
const distDir = path.resolve(entryDir, options.dist);
|
|
514
1573
|
const outputDir = path.resolve(options.output || entryDir);
|
|
515
|
-
const
|
|
516
|
-
const pkgPath = path.join(outputDir, `${
|
|
1574
|
+
const packageName = options.name || path.basename(entryDir);
|
|
1575
|
+
const pkgPath = path.join(outputDir, `${packageName}.pkg`);
|
|
517
1576
|
try {
|
|
518
|
-
await
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const childrenDir = path.join(entryDir, "children");
|
|
524
|
-
await assertPathExists(distIndexPath, `找不到 dist/index.js 文件: ${distIndexPath}`);
|
|
525
|
-
await ensureDir(outputDir);
|
|
526
|
-
const zipFile = new ZipFile();
|
|
527
|
-
const outStream = createWriteStream(pkgPath);
|
|
528
|
-
const zipPromise = new Promise((resolve, reject) => {
|
|
529
|
-
zipFile.outputStream.pipe(outStream).on("close", () => resolve());
|
|
530
|
-
zipFile.outputStream.on("error", (err) => reject(err));
|
|
531
|
-
outStream.on("error", (err) => reject(err));
|
|
1577
|
+
await buildToolPackage({
|
|
1578
|
+
entry: entryDir,
|
|
1579
|
+
output: distDir,
|
|
1580
|
+
minify: false,
|
|
1581
|
+
format: "esm"
|
|
532
1582
|
});
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (await pathExists(readmePath)) zipFile.addFile(readmePath, "README.md");
|
|
536
|
-
if (await pathExists(assetsDir)) await addDirectoryToZip(zipFile, assetsDir, "assets");
|
|
537
|
-
if (await pathExists(childrenDir)) {
|
|
538
|
-
const children = await fs.readdir(childrenDir, { withFileTypes: true });
|
|
539
|
-
for (const child of children) {
|
|
540
|
-
if (!child.isDirectory()) continue;
|
|
541
|
-
const childName = child.name;
|
|
542
|
-
const childDir = path.join(childrenDir, childName);
|
|
543
|
-
const childLogoPath = path.join(childDir, "logo.svg");
|
|
544
|
-
zipFile.addEmptyDirectory(`${childName}/`);
|
|
545
|
-
if (await pathExists(childLogoPath)) zipFile.addFile(childLogoPath, `${childName}/logo.svg`);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
zipFile.end();
|
|
549
|
-
await zipPromise;
|
|
1583
|
+
await ensureDir(outputDir);
|
|
1584
|
+
await zipDirectory(distDir, pkgPath);
|
|
550
1585
|
const duration = ((Date.now() - start) / 1e3).toFixed(2);
|
|
551
1586
|
logger.success(`打包完成,用时 ${duration}s`);
|
|
552
1587
|
logger.info(`输出文件: ${pkgPath}`);
|
|
@@ -557,34 +1592,41 @@ var PackCommand = class extends BaseCommand {
|
|
|
557
1592
|
}
|
|
558
1593
|
}
|
|
559
1594
|
};
|
|
560
|
-
async function
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
await fs.access(p);
|
|
574
|
-
} catch {
|
|
575
|
-
throw new Error(message);
|
|
1595
|
+
async function zipDirectory(sourceDir, pkgPath) {
|
|
1596
|
+
const zipFile = new ZipFile();
|
|
1597
|
+
const outStream = createWriteStream(pkgPath);
|
|
1598
|
+
const zipPromise = new Promise((resolve, reject) => {
|
|
1599
|
+
zipFile.outputStream.pipe(outStream).on("close", () => resolve());
|
|
1600
|
+
zipFile.outputStream.on("error", (err) => reject(err));
|
|
1601
|
+
outStream.on("error", (err) => reject(err));
|
|
1602
|
+
});
|
|
1603
|
+
const files = await collectFiles(sourceDir);
|
|
1604
|
+
const normalizedPkgPath = path.resolve(pkgPath);
|
|
1605
|
+
for (const file of files) {
|
|
1606
|
+
if (path.resolve(file.absolutePath) === normalizedPkgPath) continue;
|
|
1607
|
+
zipFile.addFile(file.absolutePath, file.relativePath);
|
|
576
1608
|
}
|
|
1609
|
+
zipFile.end();
|
|
1610
|
+
await zipPromise;
|
|
577
1611
|
}
|
|
578
|
-
async function
|
|
1612
|
+
async function collectFiles(dir, baseDir = dir) {
|
|
579
1613
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1614
|
+
const files = [];
|
|
580
1615
|
for (const entry of entries) {
|
|
581
|
-
const
|
|
582
|
-
const relPath = path.join(rootInZip, entry.name).split(path.sep).join("/");
|
|
1616
|
+
const absolutePath = path.join(dir, entry.name);
|
|
583
1617
|
if (entry.isDirectory()) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
1618
|
+
files.push(...await collectFiles(absolutePath, baseDir));
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
if (entry.isFile()) files.push({
|
|
1622
|
+
absolutePath,
|
|
1623
|
+
relativePath: path.relative(baseDir, absolutePath)
|
|
1624
|
+
});
|
|
587
1625
|
}
|
|
1626
|
+
return files;
|
|
1627
|
+
}
|
|
1628
|
+
async function ensureDir(dir) {
|
|
1629
|
+
await fs.mkdir(dir, { recursive: true });
|
|
588
1630
|
}
|
|
589
1631
|
|
|
590
1632
|
//#endregion
|
|
@@ -593,7 +1635,9 @@ function createProgram() {
|
|
|
593
1635
|
const program = new Command();
|
|
594
1636
|
program.name(CLI_NAME).version(CLI_VERSION).description("FastGPT 插件开发 CLI");
|
|
595
1637
|
new BuildCommand().register(program);
|
|
1638
|
+
new CheckCommand().register(program);
|
|
596
1639
|
new CreateCommand().register(program);
|
|
1640
|
+
new DebugCommand().register(program);
|
|
597
1641
|
new PackCommand().register(program);
|
|
598
1642
|
return program;
|
|
599
1643
|
}
|