@fastgpt-plugin/cli 0.1.0-alpha.1 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +9 -444
- package/index.js +3 -0
- package/package.json +11 -14
- package/templates/tool/index.ts +3 -2
- package/templates/tool/package.json +2 -1
- package/templates/tool/src/schemas.ts +0 -1
- package/templates/tool/src/tool.test.ts +3 -3
- package/templates/tool/src/tool.ts +1 -1
- package/templates/tool/tsconfig.json +17 -0
- package/templates/tool-suite/children/tool/index.ts +3 -2
- package/templates/tool-suite/children/tool/src/schemas.ts +0 -1
- package/templates/tool-suite/index.ts +2 -2
- package/templates/tool-suite/package.json +2 -1
- package/templates/tool-suite/tsconfig.json +17 -0
- package/dist/index.d.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -1,444 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
11
|
-
|
|
12
|
-
//#region package.json
|
|
13
|
-
var name = "@fastgpt-plugin/cli";
|
|
14
|
-
var version = "0.1.0";
|
|
15
|
-
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/constants.ts
|
|
18
|
-
const CLI_NAME = name;
|
|
19
|
-
const CLI_VERSION = version;
|
|
20
|
-
const DEFAULT_PLUGIN_NAME = "./packages/my-tool";
|
|
21
|
-
const DEFAULT_PLUGIN_DESCRIPTION = "This is a FastGPT plugin";
|
|
22
|
-
const TOOL_TEMPLATES_DIR = path.join(import.meta.dirname, "../templates");
|
|
23
|
-
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/helpers.ts
|
|
26
|
-
const logger = consola.withTag(CLI_NAME);
|
|
27
|
-
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region src/commands/base.ts
|
|
30
|
-
/**
|
|
31
|
-
* 所有子命令的基类:
|
|
32
|
-
* - 统一命令接口类型
|
|
33
|
-
* - 强制实现 register 方法,用于向 Commander 注册自身
|
|
34
|
-
*/
|
|
35
|
-
var BaseCommand = class {};
|
|
36
|
-
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/prompts/create/deps.ts
|
|
39
|
-
const defaultDeps = {
|
|
40
|
-
input,
|
|
41
|
-
select
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/prompts/create/input-prompt.ts
|
|
46
|
-
async function inputPrompt(deps, options) {
|
|
47
|
-
return deps.input({
|
|
48
|
-
...options,
|
|
49
|
-
validate: (value) => {
|
|
50
|
-
if (!value || value.trim().length === 0) return "请输入有效的内容";
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
//#endregion
|
|
57
|
-
//#region src/prompts/create/prompt.ts
|
|
58
|
-
var CreatePrompt = class {
|
|
59
|
-
deps;
|
|
60
|
-
constructor(deps = defaultDeps) {
|
|
61
|
-
this.deps = deps;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* 根据 CLI 传入的原始参数,交互式补全缺失字段,生成完整的 CreatePluginCommandOptions。
|
|
65
|
-
* - 如果 name、type、description 都已提供,则不进入交互流程,直接返回
|
|
66
|
-
* - 否则使用交互式 prompt 补全缺失部分
|
|
67
|
-
*/
|
|
68
|
-
async run(input) {
|
|
69
|
-
const getPluginType = () => {
|
|
70
|
-
if (!input.typeFlag) return void 0;
|
|
71
|
-
return input.typeFlag === "tool-suite" ? "tool-suite" : "tool";
|
|
72
|
-
};
|
|
73
|
-
const pluginType = getPluginType();
|
|
74
|
-
if (input.nameArg && pluginType && input.descriptionFlag !== void 0) return {
|
|
75
|
-
name: input.nameArg,
|
|
76
|
-
cwd: input.cwd,
|
|
77
|
-
type: pluginType,
|
|
78
|
-
description: input.descriptionFlag
|
|
79
|
-
};
|
|
80
|
-
const getPluginNameInputPrompt = async () => {
|
|
81
|
-
return await inputPrompt(this.deps, {
|
|
82
|
-
message: "插件名称",
|
|
83
|
-
default: DEFAULT_PLUGIN_NAME
|
|
84
|
-
});
|
|
85
|
-
};
|
|
86
|
-
const name = input.nameArg ?? await getPluginNameInputPrompt();
|
|
87
|
-
const getPluginTypeSelectPrompt = async () => {
|
|
88
|
-
return await this.deps.select({
|
|
89
|
-
message: "选择插件类型",
|
|
90
|
-
choices: [{
|
|
91
|
-
name: "单工具",
|
|
92
|
-
value: "tool",
|
|
93
|
-
description: "创建一个独立的工具"
|
|
94
|
-
}, {
|
|
95
|
-
name: "工具集",
|
|
96
|
-
value: "tool-suite",
|
|
97
|
-
description: "创建一个包含多个子工具的工具集"
|
|
98
|
-
}]
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
const type = pluginType ?? await getPluginTypeSelectPrompt();
|
|
102
|
-
const getPluginDescriptionInputPrompt = async () => {
|
|
103
|
-
return await inputPrompt(this.deps, {
|
|
104
|
-
message: "插件描述",
|
|
105
|
-
default: DEFAULT_PLUGIN_DESCRIPTION
|
|
106
|
-
});
|
|
107
|
-
};
|
|
108
|
-
const description = input.descriptionFlag ?? await getPluginDescriptionInputPrompt();
|
|
109
|
-
return {
|
|
110
|
-
name,
|
|
111
|
-
cwd: input.cwd,
|
|
112
|
-
type,
|
|
113
|
-
description
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/commands/create.ts
|
|
120
|
-
var CreateCommand = class extends BaseCommand {
|
|
121
|
-
register(parent) {
|
|
122
|
-
parent.command("create").description("创建新的 FastGPT 插件项目").argument("[name]", "插件名称").option("-t, --type <type>", "插件类型: 单工具 | 工具集").option("-d, --description <desc>", "插件描述").option("--cwd <path>", "工作目录", process.cwd()).action(async (name, opts) => {
|
|
123
|
-
const options = await new CreatePrompt().run({
|
|
124
|
-
nameArg: name,
|
|
125
|
-
typeFlag: opts.type,
|
|
126
|
-
descriptionFlag: opts.description,
|
|
127
|
-
cwd: opts.cwd
|
|
128
|
-
});
|
|
129
|
-
await this.run(options);
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
async run(options) {
|
|
133
|
-
const targetDir = path.resolve(options.cwd, options.name);
|
|
134
|
-
const templateDir = path.join(TOOL_TEMPLATES_DIR, options.type === "tool-suite" ? "tool-suite" : "tool");
|
|
135
|
-
const files = await this.collectTemplateFiles(templateDir);
|
|
136
|
-
const description = options.description ?? DEFAULT_PLUGIN_DESCRIPTION;
|
|
137
|
-
await this.ensureDir(targetDir);
|
|
138
|
-
for (const rel of files) {
|
|
139
|
-
const templatePath = path.join(templateDir, rel);
|
|
140
|
-
const filePath = path.join(targetDir, rel);
|
|
141
|
-
await this.ensureDir(path.dirname(filePath));
|
|
142
|
-
const raw = await fs.readFile(templatePath, "utf-8");
|
|
143
|
-
const content = this.applyPlaceholders(raw, options.name, description);
|
|
144
|
-
await fs.writeFile(filePath, content, "utf-8");
|
|
145
|
-
}
|
|
146
|
-
logger.success(`创建插件项目: ${options.name} (${options.type})`, { cwd: options.cwd });
|
|
147
|
-
}
|
|
148
|
-
applyPlaceholders(text, name, description) {
|
|
149
|
-
name = path.basename(name);
|
|
150
|
-
return text.replace(/\{\{name\}\}/g, name).replace(/\{\{description\}\}/g, description);
|
|
151
|
-
}
|
|
152
|
-
async collectTemplateFiles(root) {
|
|
153
|
-
const result = [];
|
|
154
|
-
const walk = async (dir) => {
|
|
155
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
156
|
-
for (const entry of entries) {
|
|
157
|
-
const full = path.join(dir, entry.name);
|
|
158
|
-
if (entry.isDirectory()) await walk(full);
|
|
159
|
-
else {
|
|
160
|
-
const rel = path.relative(root, full).replace(/\\/g, "/");
|
|
161
|
-
result.push(rel);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
await walk(root);
|
|
166
|
-
return result.sort();
|
|
167
|
-
}
|
|
168
|
-
async ensureDir(dir) {
|
|
169
|
-
await fs.mkdir(dir, { recursive: true });
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
//#endregion
|
|
174
|
-
//#region src/build/ast-transform.ts
|
|
175
|
-
const traverse = $traverse.default;
|
|
176
|
-
const generate = $generate.default;
|
|
177
|
-
function extractToolId(filePath) {
|
|
178
|
-
const stack = filePath.split("/");
|
|
179
|
-
if (stack.at(-3) === "children") return `${stack.at(-4)}/${stack.at(-2)}`;
|
|
180
|
-
return stack.at(-2) || "unknown";
|
|
181
|
-
}
|
|
182
|
-
function transformToolConfig({ sourceCode, filePath }) {
|
|
183
|
-
if (sourceCode.includes("toolId:")) return {
|
|
184
|
-
code: sourceCode,
|
|
185
|
-
hasToolId: true
|
|
186
|
-
};
|
|
187
|
-
const ast = parse(sourceCode, {
|
|
188
|
-
plugins: ["typescript"],
|
|
189
|
-
sourceType: "module"
|
|
190
|
-
});
|
|
191
|
-
const toolId = extractToolId(filePath);
|
|
192
|
-
let modified = false;
|
|
193
|
-
const childrenDir = join(filePath, "..", "children");
|
|
194
|
-
let childrenList = [];
|
|
195
|
-
if (existsSync(childrenDir)) try {
|
|
196
|
-
childrenList = readdirSync(childrenDir);
|
|
197
|
-
} catch {}
|
|
198
|
-
traverse(ast, { CallExpression(path) {
|
|
199
|
-
if (path.node.callee.type === "Identifier" && path.node.arguments[0] && path.node.arguments[0].type === "ObjectExpression") {
|
|
200
|
-
const objExpr = path.node.arguments[0];
|
|
201
|
-
let hasToolId = false;
|
|
202
|
-
traverse(path.node, { ObjectProperty(innerPath) {
|
|
203
|
-
if (innerPath.node.key.type === "Identifier" && innerPath.node.key.name === "toolId") hasToolId = true;
|
|
204
|
-
} }, path.scope, path.parentPath);
|
|
205
|
-
if (!hasToolId) {
|
|
206
|
-
const toolIdProp = {
|
|
207
|
-
type: "ObjectProperty",
|
|
208
|
-
key: {
|
|
209
|
-
type: "Identifier",
|
|
210
|
-
name: "toolId"
|
|
211
|
-
},
|
|
212
|
-
value: {
|
|
213
|
-
type: "StringLiteral",
|
|
214
|
-
value: toolId
|
|
215
|
-
},
|
|
216
|
-
computed: false,
|
|
217
|
-
shorthand: false
|
|
218
|
-
};
|
|
219
|
-
objExpr.properties.push(toolIdProp);
|
|
220
|
-
modified = true;
|
|
221
|
-
}
|
|
222
|
-
if (path.node.callee.type === "Identifier" && path.node.callee.name === "defineToolSet") {
|
|
223
|
-
let hasCustomChildren = false;
|
|
224
|
-
traverse(path.node, { ObjectProperty(innerPath) {
|
|
225
|
-
if (innerPath.node.key.type === "Identifier" && innerPath.node.key.name === "children") hasCustomChildren = true;
|
|
226
|
-
} }, path.scope, path.parentPath);
|
|
227
|
-
if (!hasCustomChildren && childrenList.length > 0) {
|
|
228
|
-
for (const child of childrenList) {
|
|
229
|
-
const importDecl = {
|
|
230
|
-
type: "ImportDeclaration",
|
|
231
|
-
source: {
|
|
232
|
-
type: "StringLiteral",
|
|
233
|
-
value: `./children/${child}`
|
|
234
|
-
},
|
|
235
|
-
importKind: "value",
|
|
236
|
-
specifiers: [{
|
|
237
|
-
type: "ImportDefaultSpecifier",
|
|
238
|
-
local: {
|
|
239
|
-
type: "Identifier",
|
|
240
|
-
name: child
|
|
241
|
-
}
|
|
242
|
-
}]
|
|
243
|
-
};
|
|
244
|
-
ast.program.body.unshift(importDecl);
|
|
245
|
-
}
|
|
246
|
-
const childrenProp = {
|
|
247
|
-
type: "ObjectProperty",
|
|
248
|
-
key: {
|
|
249
|
-
type: "Identifier",
|
|
250
|
-
name: "children"
|
|
251
|
-
},
|
|
252
|
-
value: {
|
|
253
|
-
type: "ArrayExpression",
|
|
254
|
-
elements: childrenList.map((child) => ({
|
|
255
|
-
type: "Identifier",
|
|
256
|
-
name: child
|
|
257
|
-
}))
|
|
258
|
-
},
|
|
259
|
-
computed: false,
|
|
260
|
-
shorthand: false
|
|
261
|
-
};
|
|
262
|
-
objExpr.properties.push(childrenProp);
|
|
263
|
-
modified = true;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
} });
|
|
268
|
-
return {
|
|
269
|
-
code: generate(ast).code,
|
|
270
|
-
hasToolId: !modified && sourceCode.includes("toolId:")
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
//#endregion
|
|
275
|
-
//#region src/build/index.ts
|
|
276
|
-
/**
|
|
277
|
-
* 核心构建逻辑:给定入口目录和输出目录,完成一次工具构建。
|
|
278
|
-
* - 不做日志输出
|
|
279
|
-
* - 不调用 process.exit
|
|
280
|
-
* - 失败时抛出 Error
|
|
281
|
-
*/
|
|
282
|
-
async function buildToolPackage(options) {
|
|
283
|
-
const entryDir = path.resolve(options.entry);
|
|
284
|
-
await assertPathExists(entryDir, `入口目录不存在: ${entryDir}`);
|
|
285
|
-
const configPath = path.join(entryDir, "config.ts");
|
|
286
|
-
await assertPathExists(configPath, `找不到 config.ts 文件: ${configPath}`);
|
|
287
|
-
const indexPath = path.join(entryDir, "index.ts");
|
|
288
|
-
await assertPathExists(indexPath, `找不到 index.ts 文件: ${indexPath}`);
|
|
289
|
-
const tempDir = path.join(entryDir, ".build-temp");
|
|
290
|
-
await ensureDir(tempDir);
|
|
291
|
-
try {
|
|
292
|
-
const transformed = transformToolConfig({
|
|
293
|
-
sourceCode: await fs.readFile(configPath, "utf-8"),
|
|
294
|
-
filePath: configPath
|
|
295
|
-
});
|
|
296
|
-
await fs.writeFile(path.join(tempDir, "config.ts"), transformed.code, "utf-8");
|
|
297
|
-
await copySourceFiles(entryDir, tempDir, entryDir);
|
|
298
|
-
const outputDir = path.resolve(options.output);
|
|
299
|
-
await ensureDir(outputDir);
|
|
300
|
-
await build({
|
|
301
|
-
entry: [path.join(tempDir, "index.ts")],
|
|
302
|
-
outDir: outputDir,
|
|
303
|
-
format: [options.format],
|
|
304
|
-
clean: true,
|
|
305
|
-
minify: options.minify,
|
|
306
|
-
platform: "node",
|
|
307
|
-
target: "node22",
|
|
308
|
-
dts: {
|
|
309
|
-
enabled: true,
|
|
310
|
-
sourcemap: false
|
|
311
|
-
},
|
|
312
|
-
treeshake: true,
|
|
313
|
-
skipNodeModulesBundle: process.env.NODE_ENV === "test",
|
|
314
|
-
external: process.env.NODE_ENV === "test" ? ["@fastgpt-plugin/helpers"] : []
|
|
315
|
-
});
|
|
316
|
-
return {
|
|
317
|
-
entryDir,
|
|
318
|
-
outputDir,
|
|
319
|
-
files: (await fs.readdir(outputDir)).map((f) => f)
|
|
320
|
-
};
|
|
321
|
-
} finally {
|
|
322
|
-
try {
|
|
323
|
-
await fs.rm(tempDir, {
|
|
324
|
-
recursive: true,
|
|
325
|
-
force: true
|
|
326
|
-
});
|
|
327
|
-
} catch {}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* 从入口目录复制源码到临时目录:
|
|
332
|
-
* - 根目录的 config.ts 已被转换,不再复制
|
|
333
|
-
* - 子目录中的 config.ts 需要保留(子工具自身的配置)
|
|
334
|
-
*/
|
|
335
|
-
async function copySourceFiles(sourceDir, targetDir, rootDir) {
|
|
336
|
-
const files = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
337
|
-
for (const file of files) {
|
|
338
|
-
if (file.name === "node_modules" || file.name === "dist" || file.name.startsWith(".build-")) continue;
|
|
339
|
-
const sourcePath = path.join(sourceDir, file.name);
|
|
340
|
-
const targetPath = path.join(targetDir, file.name);
|
|
341
|
-
if (file.isDirectory()) {
|
|
342
|
-
await ensureDir(targetPath);
|
|
343
|
-
await copySourceFiles(sourcePath, targetPath, rootDir);
|
|
344
|
-
} else if (file.isFile()) {
|
|
345
|
-
if (!(file.name === "config.ts" && sourceDir === rootDir)) if (file.name === "config.ts") {
|
|
346
|
-
const transformed = transformToolConfig({
|
|
347
|
-
sourceCode: await fs.readFile(sourcePath, "utf-8"),
|
|
348
|
-
filePath: sourcePath
|
|
349
|
-
});
|
|
350
|
-
await fs.writeFile(targetPath, transformed.code, "utf-8");
|
|
351
|
-
} else await fs.copyFile(sourcePath, targetPath);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
async function ensureDir(dir) {
|
|
356
|
-
await fs.mkdir(dir, { recursive: true });
|
|
357
|
-
}
|
|
358
|
-
async function assertPathExists(p, message) {
|
|
359
|
-
try {
|
|
360
|
-
await fs.access(p);
|
|
361
|
-
} catch {
|
|
362
|
-
throw new Error(message);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
//#endregion
|
|
367
|
-
//#region src/commands/build.ts
|
|
368
|
-
var BuildCommand = class extends BaseCommand {
|
|
369
|
-
register(parent) {
|
|
370
|
-
parent.command("build").description("构建 FastGPT 插件为可分发的包").option("-e, --entry <path>", "工具入口目录", process.cwd()).option("-o, --output <path>", "输出目录", "./dist").option("-m, --minify", "压缩输出代码", false).option("-w, --watch", "监听模式", false).option("-f, --format <format>", "输出格式: esm | cjs", "esm").action(async (opts) => {
|
|
371
|
-
await this.run({
|
|
372
|
-
entry: opts.entry,
|
|
373
|
-
output: opts.output,
|
|
374
|
-
minify: opts.minify,
|
|
375
|
-
watch: opts.watch,
|
|
376
|
-
format: opts.format
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
async run(options) {
|
|
381
|
-
const start = Date.now();
|
|
382
|
-
logger.box([
|
|
383
|
-
"FastGPT 插件构建",
|
|
384
|
-
"",
|
|
385
|
-
`入口目录: ${options.entry}`,
|
|
386
|
-
`输出目录: ${options.output}`,
|
|
387
|
-
`代码压缩: ${options.minify ? "开启" : "关闭"}`,
|
|
388
|
-
`输出格式: ${options.format}`,
|
|
389
|
-
""
|
|
390
|
-
].join("\n"));
|
|
391
|
-
logger.start("准备构建插件...");
|
|
392
|
-
try {
|
|
393
|
-
const result = await buildToolPackage(options);
|
|
394
|
-
const duration = ((Date.now() - start) / 1e3).toFixed(2);
|
|
395
|
-
logger.success(`构建完成,用时 ${duration}s`);
|
|
396
|
-
logger.info(`输出目录: ${result.outputDir}`);
|
|
397
|
-
if (result.files.length > 0) {
|
|
398
|
-
logger.info("生成的文件列表:");
|
|
399
|
-
result.files.forEach((file) => {
|
|
400
|
-
logger.info(` • ${file}`);
|
|
401
|
-
});
|
|
402
|
-
} else logger.info("未检测到输出文件,请检查配置。");
|
|
403
|
-
logger.info("\n下一步: 可以将该目录作为 npm 包发布,或在 FastGPT 中作为插件安装使用。");
|
|
404
|
-
if (options.watch) logger.info("\n监听模式暂不支持,请使用 --no-watch 选项");
|
|
405
|
-
} catch (error) {
|
|
406
|
-
logger.error("构建失败,详情如下:");
|
|
407
|
-
logger.error(error instanceof Error ? error : new Error(String(error)));
|
|
408
|
-
logger.info("如果这是在 CI 中发生的,请查看上方构建日志以获取更多信息。");
|
|
409
|
-
process.exit(1);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
//#endregion
|
|
415
|
-
//#region src/cmd.ts
|
|
416
|
-
function createProgram() {
|
|
417
|
-
const program = new Command();
|
|
418
|
-
program.name(CLI_NAME).version(CLI_VERSION).description("FastGPT 插件开发 CLI");
|
|
419
|
-
new CreateCommand().register(program);
|
|
420
|
-
new BuildCommand().register(program);
|
|
421
|
-
return program;
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* 运行 CLI,解析 process.argv。
|
|
425
|
-
* @param argv 可选,用于测试时注入参数(如 ['node', 'cli', '--version'])
|
|
426
|
-
*/
|
|
427
|
-
async function run(argv) {
|
|
428
|
-
await createProgram().parseAsync(argv ?? process.argv);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
//#endregion
|
|
432
|
-
//#region src/index.ts
|
|
433
|
-
const main = async () => {
|
|
434
|
-
try {
|
|
435
|
-
await run();
|
|
436
|
-
} catch (error) {
|
|
437
|
-
logger.error(error);
|
|
438
|
-
process.exitCode = 1;
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
if (import.meta.main) main();
|
|
442
|
-
|
|
443
|
-
//#endregion
|
|
444
|
-
export { };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{consola as e}from"consola/basic";import{Command as t}from"commander";import n from"node:path";import r from"node:fs/promises";import{input as i,select as a}from"@inquirer/prompts";import{kebabCase as o}from"es-toolkit";import{build as s}from"tsdown";import{Visitor as c,parseSync as l}from"oxc-parser";import{createHash as u}from"node:crypto";const d=e;var f=`@fastgpt-plugin/cli`,p=`0.1.0-alpha.10`;const m=f,h=p,g=`This is a FastGPT plugin`,_=n.join(import.meta.dirname,`../templates`);var v=class{};const y={input:i,select:a};async function b(e,t){return e.input({...t,validate:e=>!e||e.trim().length===0?`请输入有效的内容`:!0})}var x=class{deps;constructor(e=y){this.deps=e}async run(e){let t=(()=>{if(e.typeFlag)return e.typeFlag===`tool-suite`?`tool-suite`:`tool`})();if(e.nameArg&&t&&e.descriptionFlag!==void 0)return{name:e.nameArg,cwd:e.cwd,type:t,description:e.descriptionFlag};let n=e.nameArg??await(async()=>await b(this.deps,{message:`插件名称`,default:`./packages/my-tool`}))(),r=t??await(async()=>await this.deps.select({message:`选择插件类型`,choices:[{name:`单工具`,value:`tool`,description:`创建一个独立的工具`},{name:`工具集`,value:`tool-suite`,description:`创建一个包含多个子工具的工具集`}]}))(),i=e.descriptionFlag??await(async()=>await b(this.deps,{message:`插件描述`,default:g}))();return{name:n,cwd:e.cwd,type:r,description:i}}},S=class extends v{register(e){e.command(`create`).description(`创建新的 FastGPT 插件项目`).argument(`[name]`,`插件名称`).option(`-t, --type <type>`,`插件类型: 单工具 | 工具集`).option(`-d, --description <desc>`,`插件描述`).option(`--cwd <path>`,`工作目录`,process.cwd()).action(async(e,t)=>{let n=await new x().run({nameArg:e,typeFlag:t.type,descriptionFlag:t.description,cwd:t.cwd});await this.run(n)})}async run(e){let t=n.resolve(e.cwd,e.name),i=n.join(_,e.type===`tool-suite`?`tool-suite`:`tool`),a=await this.collectTemplateFiles(i),o=e.description??g;await this.ensureDir(t);for(let s of a){let a=n.join(i,s),c=n.join(t,s);await this.ensureDir(n.dirname(c));let l=await r.readFile(a,`utf-8`),u=this.applyPlaceholders(l,e.name,o);await r.writeFile(c,u,`utf-8`)}d.success(`创建插件项目: ${e.name} (${e.type})`,{cwd:e.cwd})}applyPlaceholders(e,t,r){return t=n.basename(t),t=o(t),e.replace(/\{\{name\}\}/g,t).replace(/\{\{description\}\}/g,r)}async collectTemplateFiles(e){let t=[],i=async a=>{let o=await r.readdir(a,{withFileTypes:!0});for(let r of o){let o=n.join(a,r.name);if(r.isDirectory())await i(o);else{let r=n.relative(e,o).replace(/\\/g,`/`);t.push(r)}}};return await i(e),t.sort()}async ensureDir(e){await r.mkdir(e,{recursive:!0})}};async function C(e){let t=n.join(e.rootDir,`src`);if(!await T(t))return E(``);let i=[],a=async t=>{let o=await r.readdir(t,{withFileTypes:!0});for(let r of o){let o=n.join(t,r.name);if(r.isDirectory()){if(r.name===`__tests__`)continue;await a(o);continue}if(!r.isFile())continue;let s=r.name;if(s.includes(`.test.`)||s.includes(`.spec.`))continue;let c=n.relative(e.rootDir,o).split(n.sep).join(`/`);c.startsWith(`src/`)&&i.push({rel:c,abs:o})}};await a(t),i.sort((e,t)=>e.rel.localeCompare(t.rel));let o=``;for(let e of i){let t=E(await r.readFile(e.abs));o+=`${e.rel}:${t}\n`}return E(o)}async function w(e){let{rootDir:t,toolId:r}=e,i=[...e.children].sort(),a=[];for(let e of i){let i=await C({rootDir:n.join(t,`children`,e)});a.push(`${r}/${e}:${i}`)}return E(a.length>0?`${a.join(`
|
|
3
|
+
`)}\n`:``)}async function T(e){try{return await r.access(e),!0}catch{return!1}}function E(e){let t=u(`md5`);return t.update(e),t.digest(`hex`)}function D(e){let t=e.split(`/`);return t.at(-3)===`children`?`${t.at(-4)}/${t.at(-2)}`:t.at(-2)||`unknown`}async function O(e){let t=n.join(e,`children`);try{await r.access(t)}catch{return[]}try{return await r.readdir(t)}catch{return[]}}function k(e,t){return e.some(e=>e.type===`Property`&&e.key?.type===`Identifier`&&e.key?.name===t)}function A(e,t){let n=t.sort((e,t)=>t.start-e.start),r=e;for(let e of n)r=r.slice(0,e.start)+e.content+r.slice(e.end);return r}async function j({sourceCode:e,filePath:t}){if(e.includes(`toolId:`))return{code:e,hasToolId:!0};let r=D(t),i=n.dirname(t),a=await O(i),o=await C({rootDir:i}),s=a.length>0?await w({rootDir:i,toolId:r,children:a}):null,u=l(n.basename(t),e,{sourceType:`module`});if(u.errors.length>0)throw Error(`Parse errors: ${u.errors.map(e=>e.message).join(`, `)}`);let d=[],f=[];new c({CallExpression(e){e.callee?.type===`Identifier`&&(e.callee.name===`defineTool`||e.callee.name===`defineToolSet`)&&e.arguments?.[0]?.type===`ObjectExpression`&&f.push(e)}}).visit(u.program);for(let e of f){let t=e.arguments[0];if(t?.type!==`ObjectExpression`)continue;let n=t,i=n.properties||[],c=e.callee?.type===`Identifier`&&e.callee.name===`defineToolSet`,l=!k(i,`toolId`),u=!k(i,`etag`),f=c&&!k(i,`children`)&&a.length>0;if(!l&&!u&&!f)continue;let p=i[i.length-1],m=p?p.end:(n.start??0)+1,h=[];if(l&&h.push(`toolId: '${r}'`),u){let e=c&&s?s:o;h.push(`etag: '${e}'`)}if(f){let e=a.map(e=>e).join(`, `);h.push(`children: [${e}]`);let t=a.map(e=>`import ${e} from './children/${e}';`).join(`
|
|
4
|
+
`);d.push({start:0,end:0,content:t+`
|
|
5
|
+
`})}if(h.length>0){let e=(p?`,
|
|
6
|
+
`:`
|
|
7
|
+
`)+h.join(`,
|
|
8
|
+
`);d.push({start:m,end:m,content:e})}}return d.length===0?{code:e,hasToolId:!1}:{code:A(e,d),hasToolId:!0}}async function M(e){let t=n.resolve(e.entry);await F(t,`入口目录不存在: ${t}`);let i=n.join(t,`config.ts`);await F(i,`找不到 config.ts 文件: ${i}`);let a=n.join(t,`index.ts`);await F(a,`找不到 index.ts 文件: ${a}`);let o=n.join(t,`.build-temp`);await P(o);let c=Date.now(),l=0,u=0,f=0;try{let a=Date.now(),p=await j({sourceCode:await r.readFile(i,`utf-8`),filePath:i});await r.writeFile(n.join(o,`config.ts`),p.code,`utf-8`),l=Date.now()-a;let m=Date.now();await N(t,o,t),u=Date.now()-m;let h=n.resolve(e.output);await P(h);let g=n.join(o,`index.ts`),_=Date.now();await s({entry:[g],outDir:h,format:[e.format],clean:!0,minify:e.minify,inlineOnly:!1,nodeProtocol:!0,platform:`node`,target:`node22`,dts:!1,treeshake:!0,noExternal:[`*`],outExtensions:()=>({dts:`.d.ts`,js:`.js`})}),f=Date.now()-_;let v=await r.readdir(h),y=Date.now()-c;return d.info([`构建阶段耗时统计:`,`• config 转换: ${I(l)}`,`• 源码复制: ${I(u)}`,`• tsdown 构建: ${I(f)}`,`• 总耗时: ${I(y)}`].join(`
|
|
9
|
+
`)),{entryDir:t,outputDir:h,files:v.map(e=>e)}}finally{try{await r.rm(o,{recursive:!0,force:!0})}catch{}}}async function N(e,t,i){let a=await r.readdir(e,{withFileTypes:!0});for(let o of a){if(o.name===`node_modules`||o.name===`dist`||o.name.startsWith(`.build-`))continue;let a=n.join(e,o.name),s=n.join(t,o.name);if(o.isDirectory())await P(s),await N(a,s,i);else if(o.isFile()&&!(o.name===`config.ts`&&e===i))if(o.name===`config.ts`){let e=await j({sourceCode:await r.readFile(a,`utf-8`),filePath:a});await r.writeFile(s,e.code,`utf-8`)}else await r.copyFile(a,s)}}async function P(e){await r.mkdir(e,{recursive:!0})}async function F(e,t){try{await r.access(e)}catch{throw Error(t)}}function I(e){return e<1e3?`${e}ms`:`${(e/1e3).toFixed(2)}s`}var L=class extends v{register(e){e.command(`build`).description(`构建 FastGPT 插件为可分发的包`).option(`-e, --entry <path>`,`工具入口目录`,process.cwd()).option(`-o, --output <path>`,`输出目录`,`./dist`).option(`-m, --minify`,`压缩输出代码`,!1).option(`-f, --format <format>`,`输出格式: esm | cjs`,`esm`).action(async e=>{await this.run({entry:e.entry,output:e.output,minify:e.minify,format:e.format})})}async run(e){let t=Date.now();d.start(`准备构建插件...`);try{let n=await M(e),r=((Date.now()-t)/1e3).toFixed(2);d.success(`构建完成,用时 ${r}s`),d.info(`输出目录: ${n.outputDir}`),n.files.length>0?(d.info(`生成的文件列表:`),n.files.forEach(e=>{d.info(`• ${e}`)})):d.info(`未检测到输出文件,请检查配置。`)}catch(e){d.error(`构建失败,详情如下:`),d.error(e instanceof Error?e:Error(String(e))),d.info(`如果这是在 CI 中发生的,请查看上方构建日志以获取更多信息。`),process.exit(1)}}};function R(){let e=new t;return e.name(m).version(h).description(`FastGPT 插件开发 CLI`),new S().register(e),new L().register(e),e}async function z(e){await R().parseAsync(e??process.argv)}process.on(`SIGINT`,()=>{process.exit(130)}),(async()=>{try{await z()}catch(e){e instanceof Error&&(e.name===`ExitPromptError`||e.message.includes(`SIGINT`)||e.message.includes(`force closed`))&&process.exit(130),d.error(e),process.exitCode=1}})();export{};
|
package/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastgpt-plugin/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"@fastgpt-plugin/cli": "
|
|
10
|
+
"@fastgpt-plugin/cli": "index.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"LICENSE",
|
|
17
|
-
"package.json"
|
|
14
|
+
"index.js",
|
|
15
|
+
"templates"
|
|
18
16
|
],
|
|
19
17
|
"scripts": {
|
|
20
18
|
"build": "tsdown",
|
|
19
|
+
"build:verbose": "tsdown",
|
|
21
20
|
"test": "vitest run",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"dev": "bun run src/index.ts",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
24
23
|
},
|
|
25
24
|
"dependencies": {
|
|
26
|
-
"@
|
|
27
|
-
"
|
|
28
|
-
"@babel/traverse": "^7.26.5",
|
|
29
|
-
"@babel/types": "^7.26.5",
|
|
25
|
+
"@oxc-parser/binding-wasm32-wasi": "^0.112.0",
|
|
26
|
+
"oxc-parser": "^0.112.0",
|
|
30
27
|
"@inquirer/prompts": "^8.2.0",
|
|
31
28
|
"commander": "^14.0.3",
|
|
32
29
|
"consola": "^3.4.2",
|
|
30
|
+
"es-toolkit": "^1.44.0",
|
|
33
31
|
"tsdown": "^0.20.1"
|
|
34
32
|
},
|
|
35
33
|
"devDependencies": {
|
|
36
|
-
"@types/bun": "latest",
|
|
37
34
|
"typescript": "^5",
|
|
38
35
|
"vite-tsconfig-paths": "^6.0.5",
|
|
39
36
|
"vitest": "^4.0.18"
|
|
40
37
|
},
|
|
41
38
|
"engines": {
|
|
42
|
-
"node": ">=22.
|
|
39
|
+
"node": ">=22.12.0"
|
|
43
40
|
}
|
|
44
41
|
}
|
package/templates/tool/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exportTool } from '@fastgpt-plugin/helpers/tools/helper';
|
|
2
|
-
import config from './config
|
|
3
|
-
import {
|
|
2
|
+
import config from './config';
|
|
3
|
+
import { tool as toolCb } from './src/tool';
|
|
4
|
+
import { InputType, OutputType } from './src/schemas';
|
|
4
5
|
|
|
5
6
|
export default exportTool({
|
|
6
7
|
toolCb,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "
|
|
8
|
+
"build": "npx @fastgpt-plugin/cli build",
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"zod": "catalog:"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
+
"@fastgpt-plugin/cli": "catalog:",
|
|
16
17
|
"typescript": "catalog:",
|
|
17
18
|
"vitest": "catalog:"
|
|
18
19
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe,
|
|
2
|
-
import { tool } from './tool';
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
3
2
|
import { InputType, OutputType } from './schemas';
|
|
3
|
+
import { tool } from './tool';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('get-time', () => {
|
|
6
6
|
it('should run with valid IO schemas', async () => {
|
|
7
7
|
const input = InputType.parse({});
|
|
8
8
|
const result = await tool(input, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RunToolSecondParamsType } from '@fastgpt-plugin/helpers/tools/schemas/req';
|
|
2
2
|
import type { z } from 'zod';
|
|
3
|
-
import { InputType, OutputType } from './schemas';
|
|
3
|
+
import type { InputType, OutputType } from './schemas';
|
|
4
4
|
|
|
5
5
|
export async function tool(
|
|
6
6
|
_input: z.infer<typeof InputType>,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "esnext",
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"types": [],
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noUncheckedSideEffectImports": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"skipLibCheck": true
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exportTool } from '@fastgpt-plugin/helpers/tools/helper';
|
|
2
|
-
import config from './config
|
|
3
|
-
import {
|
|
2
|
+
import config from './config';
|
|
3
|
+
import { tool as toolCb } from './src/tool';
|
|
4
|
+
import { InputType, OutputType } from './src/schemas';
|
|
4
5
|
|
|
5
6
|
export default exportTool({
|
|
6
7
|
toolCb,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as config } from './config
|
|
2
|
-
export {
|
|
1
|
+
export { default as config } from './config';
|
|
2
|
+
export { default as toolFromChild } from './children/tool/index';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "
|
|
8
|
+
"build": "npx @fastgpt-plugin/cli build",
|
|
9
9
|
"test": "vitest run"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"zod": "catalog:"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
+
"@fastgpt-plugin/cli": "catalog:",
|
|
16
17
|
"typescript": "catalog:",
|
|
17
18
|
"vitest": "catalog:"
|
|
18
19
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "esnext",
|
|
4
|
+
"target": "es2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"types": [],
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"noUncheckedSideEffectImports": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"skipLibCheck": true
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { };
|