@spaceflow/core 0.8.0 → 0.10.0
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 +75 -56
- package/dist/index.js +5589 -970
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
- package/src/cli-runtime/di/config.ts +157 -0
- package/src/cli-runtime/di/container.ts +120 -0
- package/src/cli-runtime/di/index.ts +3 -0
- package/src/cli-runtime/di/services.ts +53 -0
- package/src/cli-runtime/extension-loader.ts +74 -0
- package/src/{shared → cli-runtime}/i18n/index.ts +7 -1
- package/src/cli-runtime/i18n/init.ts +117 -0
- package/src/cli-runtime/index.ts +131 -0
- package/src/cli-runtime/internal-extensions.ts +33 -0
- package/src/commands/build/build.service.ts +323 -0
- package/src/commands/build/index.ts +49 -0
- package/src/commands/clear/clear.service.ts +159 -0
- package/src/commands/clear/index.ts +35 -0
- package/src/commands/commit/commit.config.ts +168 -0
- package/src/commands/commit/commit.service.ts +950 -0
- package/src/commands/commit/index.ts +58 -0
- package/src/commands/create/create.service.ts +318 -0
- package/src/commands/create/index.ts +42 -0
- package/src/commands/dev/index.ts +30 -0
- package/src/commands/install/index.ts +65 -0
- package/src/commands/install/install.service.ts +1539 -0
- package/src/commands/list/index.ts +33 -0
- package/src/commands/list/list.service.ts +127 -0
- package/src/commands/mcp/index.ts +37 -0
- package/src/commands/mcp/mcp.service.ts +246 -0
- package/src/commands/runx/index.ts +47 -0
- package/src/commands/runx/runx.service.ts +142 -0
- package/src/commands/runx/runx.utils.ts +83 -0
- package/src/commands/schema/index.ts +30 -0
- package/src/commands/setup/index.ts +34 -0
- package/src/commands/setup/setup.service.ts +234 -0
- package/src/commands/uninstall/index.ts +42 -0
- package/src/commands/uninstall/uninstall.service.ts +166 -0
- package/src/commands/update/index.ts +42 -0
- package/src/commands/update/update.service.ts +373 -0
- package/src/config/index.ts +1 -30
- package/src/config/spaceflow.config.ts +226 -278
- package/src/index.ts +11 -1
- package/src/locales/en/build.json +22 -0
- package/src/locales/en/clear.json +16 -0
- package/src/locales/en/commit.json +45 -0
- package/src/locales/en/create.json +27 -0
- package/src/locales/en/dev.json +5 -0
- package/src/locales/en/install.json +71 -0
- package/src/locales/en/list.json +8 -0
- package/src/locales/en/mcp.json +19 -0
- package/src/locales/en/runx.json +13 -0
- package/src/locales/en/schema.json +4 -0
- package/src/locales/en/setup.json +14 -0
- package/src/locales/en/uninstall.json +18 -0
- package/src/locales/en/update.json +28 -0
- package/src/locales/zh-cn/build.json +22 -0
- package/src/locales/zh-cn/clear.json +16 -0
- package/src/locales/zh-cn/commit.json +45 -0
- package/src/locales/zh-cn/create.json +27 -0
- package/src/locales/zh-cn/dev.json +5 -0
- package/src/locales/zh-cn/install.json +71 -0
- package/src/locales/zh-cn/list.json +8 -0
- package/src/locales/zh-cn/mcp.json +19 -0
- package/src/locales/zh-cn/runx.json +13 -0
- package/src/locales/zh-cn/schema.json +4 -0
- package/src/locales/zh-cn/setup.json +14 -0
- package/src/locales/zh-cn/uninstall.json +18 -0
- package/src/locales/zh-cn/update.json +28 -0
- package/src/shared/editor-config/index.ts +2 -21
- package/src/shared/llm-proxy/adapters/openai.adapter.ts +3 -1
- package/src/shared/package-manager/index.ts +5 -76
- package/src/shared/source-utils/index.ts +12 -130
- package/src/shared/spaceflow-dir/index.ts +13 -135
- package/src/shared/verbose/index.ts +10 -87
- package/dist/524.js +0 -9
- package/src/config/ci.config.ts +0 -29
- package/src/config/config-loader.ts +0 -100
- package/src/config/config-reader.service.ts +0 -128
- package/src/config/config-reader.ts +0 -75
- package/src/config/feishu.config.ts +0 -35
- package/src/config/git-provider.config.ts +0 -29
- package/src/config/llm.config.ts +0 -110
- package/src/config/load-env.ts +0 -15
- package/src/config/storage.config.ts +0 -33
- /package/src/{shared → cli-runtime}/i18n/i18n.spec.ts +0 -0
- /package/src/{shared → cli-runtime}/i18n/i18n.ts +0 -0
- /package/src/{shared → cli-runtime}/i18n/locale-detect.ts +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { defineExtension, type LlmProxyService } from "@spaceflow/core";
|
|
2
|
+
import { CommitService } from "./commit.service";
|
|
3
|
+
import { CommitScopeConfigSchema } from "./commit.config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Commit 命令扩展
|
|
7
|
+
*/
|
|
8
|
+
export const commitExtension = defineExtension({
|
|
9
|
+
name: "commit",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
description: "提交代码",
|
|
12
|
+
configKey: "commit",
|
|
13
|
+
configSchema: () => CommitScopeConfigSchema,
|
|
14
|
+
commands: [
|
|
15
|
+
{
|
|
16
|
+
name: "commit",
|
|
17
|
+
description: "AI 辅助生成 commit message",
|
|
18
|
+
options: [
|
|
19
|
+
{
|
|
20
|
+
flags: "-m, --message <message>",
|
|
21
|
+
description: "直接使用指定的提交信息",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
flags: "-s, --split",
|
|
25
|
+
description: "自动拆分提交",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
flags: "--dry-run",
|
|
29
|
+
description: "仅生成 message,不执行提交",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
flags: "-v, --verbose",
|
|
33
|
+
description: "详细输出",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
run: async (_args, options, ctx) => {
|
|
37
|
+
const configReader = ctx.config;
|
|
38
|
+
const llmProxy = ctx.getService<LlmProxyService>("llmProxy");
|
|
39
|
+
if (!llmProxy) {
|
|
40
|
+
ctx.output.error("commit 命令需要配置 LLM 服务,请在 spaceflow.json 中配置 llm 字段");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const commitService = new CommitService(configReader, llmProxy);
|
|
44
|
+
const verbose = options?.verbose ? 2 : 1;
|
|
45
|
+
if (options?.message) {
|
|
46
|
+
await commitService.commit(options.message as string, { verbose });
|
|
47
|
+
} else if (options?.split) {
|
|
48
|
+
await commitService.commitInBatches({ verbose, dryRun: !!options?.dryRun });
|
|
49
|
+
} else {
|
|
50
|
+
await commitService.generateAndCommit({ verbose, dryRun: !!options?.dryRun });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export * from "./commit.config";
|
|
58
|
+
export * from "./commit.service";
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { mkdir, writeFile, readFile, readdir, stat } from "fs/promises";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { shouldLog, type VerboseLevel, t } from "@spaceflow/core";
|
|
8
|
+
|
|
9
|
+
export interface CreateOptions {
|
|
10
|
+
directory?: string;
|
|
11
|
+
from?: string;
|
|
12
|
+
ref?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface TemplateContext {
|
|
16
|
+
name: string;
|
|
17
|
+
pascalName: string;
|
|
18
|
+
camelName: string;
|
|
19
|
+
kebabName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 创建插件服务
|
|
24
|
+
*/
|
|
25
|
+
export class CreateService {
|
|
26
|
+
// 缓存当前使用的远程模板目录
|
|
27
|
+
private remoteTemplatesDir: string | null = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 获取缓存目录路径
|
|
31
|
+
*/
|
|
32
|
+
protected getCacheDir(): string {
|
|
33
|
+
return join(homedir(), ".cache", "spaceflow", "templates");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 计算仓库 URL 的哈希值(用于缓存目录名)
|
|
38
|
+
*/
|
|
39
|
+
protected getRepoHash(repoUrl: string): string {
|
|
40
|
+
return createHash("md5").update(repoUrl).digest("hex").slice(0, 12);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 从仓库 URL 提取名称
|
|
45
|
+
*/
|
|
46
|
+
protected getRepoName(repoUrl: string): string {
|
|
47
|
+
// 处理 git@host:org/repo.git 或 https://host/org/repo.git
|
|
48
|
+
const match = repoUrl.match(/[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
49
|
+
if (match) {
|
|
50
|
+
return match[1].replace("/", "-");
|
|
51
|
+
}
|
|
52
|
+
return this.getRepoHash(repoUrl);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 确保远程模板仓库已克隆/更新
|
|
57
|
+
*/
|
|
58
|
+
async ensureRemoteTemplates(
|
|
59
|
+
repoUrl: string,
|
|
60
|
+
ref?: string,
|
|
61
|
+
verbose: VerboseLevel = 1,
|
|
62
|
+
): Promise<string> {
|
|
63
|
+
const cacheDir = this.getCacheDir();
|
|
64
|
+
const repoName = this.getRepoName(repoUrl);
|
|
65
|
+
const repoHash = this.getRepoHash(repoUrl);
|
|
66
|
+
const targetDir = join(cacheDir, `${repoName}-${repoHash}`);
|
|
67
|
+
|
|
68
|
+
// 确保缓存目录存在
|
|
69
|
+
await mkdir(cacheDir, { recursive: true });
|
|
70
|
+
|
|
71
|
+
if (existsSync(targetDir)) {
|
|
72
|
+
// 已存在,更新仓库
|
|
73
|
+
if (shouldLog(verbose, 1)) console.log(t("create:updatingRepo", { url: repoUrl }));
|
|
74
|
+
try {
|
|
75
|
+
execSync("git fetch --all", { cwd: targetDir, stdio: "pipe" });
|
|
76
|
+
if (ref) {
|
|
77
|
+
execSync(`git checkout ${ref}`, { cwd: targetDir, stdio: "pipe" });
|
|
78
|
+
// 如果是分支,尝试 pull
|
|
79
|
+
try {
|
|
80
|
+
execSync(`git pull origin ${ref}`, { cwd: targetDir, stdio: "pipe" });
|
|
81
|
+
} catch {
|
|
82
|
+
// 可能是 tag,忽略 pull 错误
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
execSync("git pull", { cwd: targetDir, stdio: "pipe" });
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (shouldLog(verbose, 1)) console.warn(t("create:updateFailed"));
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// 不存在,克隆仓库
|
|
92
|
+
if (shouldLog(verbose, 1)) console.log(t("create:cloningRepo", { url: repoUrl }));
|
|
93
|
+
try {
|
|
94
|
+
execSync(`git clone ${repoUrl} ${targetDir}`, { stdio: "pipe" });
|
|
95
|
+
if (ref) {
|
|
96
|
+
execSync(`git checkout ${ref}`, { cwd: targetDir, stdio: "pipe" });
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
t("create:cloneFailed", { error: error instanceof Error ? error.message : error }),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 设置当前使用的远程模板目录
|
|
106
|
+
this.remoteTemplatesDir = targetDir;
|
|
107
|
+
if (shouldLog(verbose, 1)) console.log(t("create:repoReady", { dir: targetDir }));
|
|
108
|
+
return targetDir;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 获取模板目录路径
|
|
113
|
+
*/
|
|
114
|
+
protected getTemplatesDir(options?: CreateOptions): string {
|
|
115
|
+
// 如果有远程模板目录,优先使用
|
|
116
|
+
if (options?.from && this.remoteTemplatesDir) {
|
|
117
|
+
return this.remoteTemplatesDir;
|
|
118
|
+
}
|
|
119
|
+
// 尝试从项目根目录查找 templates
|
|
120
|
+
const cwd = process.cwd();
|
|
121
|
+
const templatesInCwd = join(cwd, "templates");
|
|
122
|
+
if (existsSync(templatesInCwd)) {
|
|
123
|
+
return templatesInCwd;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 尝试从父目录查找(在 core 子目录运行时)
|
|
127
|
+
const templatesInParent = join(cwd, "..", "templates");
|
|
128
|
+
if (existsSync(templatesInParent)) {
|
|
129
|
+
return resolve(templatesInParent);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 尝试从 spaceflow 包目录查找
|
|
133
|
+
const spaceflowRoot = join(cwd, "node_modules", "spaceflow", "templates");
|
|
134
|
+
if (existsSync(spaceflowRoot)) {
|
|
135
|
+
return spaceflowRoot;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 回退到相对于当前文件的路径(开发模式)
|
|
139
|
+
// 从 core/dist/commands/create 回溯到项目根目录
|
|
140
|
+
const devPath = resolve(__dirname, "..", "..", "..", "..", "templates");
|
|
141
|
+
if (existsSync(devPath)) {
|
|
142
|
+
return devPath;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error(t("create:templatesDirNotFound"));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 获取可用的模板列表
|
|
150
|
+
*/
|
|
151
|
+
async getAvailableTemplates(options?: CreateOptions): Promise<string[]> {
|
|
152
|
+
try {
|
|
153
|
+
const templatesDir = this.getTemplatesDir(options);
|
|
154
|
+
const entries = await readdir(templatesDir);
|
|
155
|
+
const templates: string[] = [];
|
|
156
|
+
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
// 跳过隐藏目录
|
|
159
|
+
if (entry.startsWith(".")) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const entryPath = join(templatesDir, entry);
|
|
163
|
+
const entryStat = await stat(entryPath);
|
|
164
|
+
if (entryStat.isDirectory()) {
|
|
165
|
+
templates.push(entry);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return templates;
|
|
170
|
+
} catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 基于模板创建插件
|
|
177
|
+
*/
|
|
178
|
+
async createFromTemplate(
|
|
179
|
+
template: string,
|
|
180
|
+
name: string,
|
|
181
|
+
options: CreateOptions,
|
|
182
|
+
verbose: VerboseLevel = 1,
|
|
183
|
+
): Promise<void> {
|
|
184
|
+
const cwd = process.cwd();
|
|
185
|
+
// 默认目录为 <template>/<name>
|
|
186
|
+
const targetDir = options.directory || join(template, name);
|
|
187
|
+
const fullPath = join(cwd, targetDir);
|
|
188
|
+
|
|
189
|
+
if (shouldLog(verbose, 1)) {
|
|
190
|
+
console.log(t("create:creatingPlugin", { template, name }));
|
|
191
|
+
console.log(t("create:targetDir", { dir: targetDir }));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 检查目录是否已存在
|
|
195
|
+
if (existsSync(fullPath)) {
|
|
196
|
+
throw new Error(t("create:dirExists", { dir: targetDir }));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 从模板生成文件
|
|
200
|
+
const templatesDir = this.getTemplatesDir(options);
|
|
201
|
+
const templateDir = join(templatesDir, template);
|
|
202
|
+
|
|
203
|
+
if (!existsSync(templateDir)) {
|
|
204
|
+
const available = await this.getAvailableTemplates(options);
|
|
205
|
+
throw new Error(
|
|
206
|
+
t("create:templateNotFound", {
|
|
207
|
+
template,
|
|
208
|
+
available: available.join(", ") || t("create:noTemplatesAvailable"),
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const context = this.createContext(name);
|
|
214
|
+
await this.copyTemplateDir(templateDir, fullPath, context, verbose);
|
|
215
|
+
|
|
216
|
+
if (shouldLog(verbose, 1)) {
|
|
217
|
+
console.log(t("create:pluginCreated", { template, name }));
|
|
218
|
+
console.log("");
|
|
219
|
+
console.log(t("create:nextSteps"));
|
|
220
|
+
console.log(` cd ${targetDir}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 创建模板上下文
|
|
226
|
+
*/
|
|
227
|
+
protected createContext(name: string): TemplateContext {
|
|
228
|
+
return {
|
|
229
|
+
name,
|
|
230
|
+
pascalName: this.toPascalCase(name),
|
|
231
|
+
camelName: this.toCamelCase(name),
|
|
232
|
+
kebabName: this.toKebabCase(name),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 递归复制模板目录
|
|
238
|
+
*/
|
|
239
|
+
protected async copyTemplateDir(
|
|
240
|
+
templateDir: string,
|
|
241
|
+
targetDir: string,
|
|
242
|
+
context: TemplateContext,
|
|
243
|
+
verbose: VerboseLevel = 1,
|
|
244
|
+
): Promise<void> {
|
|
245
|
+
// 确保目标目录存在
|
|
246
|
+
await mkdir(targetDir, { recursive: true });
|
|
247
|
+
|
|
248
|
+
const entries = await readdir(templateDir);
|
|
249
|
+
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const templatePath = join(templateDir, entry);
|
|
252
|
+
const entryStat = await stat(templatePath);
|
|
253
|
+
|
|
254
|
+
if (entryStat.isDirectory()) {
|
|
255
|
+
// 递归处理子目录
|
|
256
|
+
const targetSubDir = join(targetDir, entry);
|
|
257
|
+
await this.copyTemplateDir(templatePath, targetSubDir, context, verbose);
|
|
258
|
+
} else if (entry.endsWith(".hbs")) {
|
|
259
|
+
// 处理模板文件
|
|
260
|
+
const content = await readFile(templatePath, "utf-8");
|
|
261
|
+
const rendered = this.renderTemplate(content, context);
|
|
262
|
+
|
|
263
|
+
// 计算目标文件名(移除 .hbs 后缀,替换 __name__ 占位符)
|
|
264
|
+
let targetFileName = entry.slice(0, -4); // 移除 .hbs
|
|
265
|
+
targetFileName = targetFileName.replace(/__name__/g, context.kebabName);
|
|
266
|
+
|
|
267
|
+
const targetPath = join(targetDir, targetFileName);
|
|
268
|
+
await writeFile(targetPath, rendered);
|
|
269
|
+
if (shouldLog(verbose, 1)) console.log(t("create:fileCreated", { file: targetFileName }));
|
|
270
|
+
} else {
|
|
271
|
+
// 直接复制非模板文件
|
|
272
|
+
const content = await readFile(templatePath);
|
|
273
|
+
const targetPath = join(targetDir, entry);
|
|
274
|
+
await writeFile(targetPath, content);
|
|
275
|
+
if (shouldLog(verbose, 1)) console.log(t("create:fileCopied", { file: entry }));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 渲染模板(简单的 Handlebars 风格替换)
|
|
282
|
+
*/
|
|
283
|
+
protected renderTemplate(template: string, context: TemplateContext): string {
|
|
284
|
+
return template
|
|
285
|
+
.replace(/\{\{name\}\}/g, context.name)
|
|
286
|
+
.replace(/\{\{pascalName\}\}/g, context.pascalName)
|
|
287
|
+
.replace(/\{\{camelName\}\}/g, context.camelName)
|
|
288
|
+
.replace(/\{\{kebabName\}\}/g, context.kebabName);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 转换为 PascalCase
|
|
293
|
+
*/
|
|
294
|
+
protected toPascalCase(str: string): string {
|
|
295
|
+
return str
|
|
296
|
+
.split(/[-_]/)
|
|
297
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
298
|
+
.join("");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 转换为 camelCase
|
|
303
|
+
*/
|
|
304
|
+
protected toCamelCase(str: string): string {
|
|
305
|
+
const pascal = this.toPascalCase(str);
|
|
306
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 转换为 kebab-case
|
|
311
|
+
*/
|
|
312
|
+
protected toKebabCase(str: string): string {
|
|
313
|
+
return str
|
|
314
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
|
315
|
+
.replace(/[_\s]+/g, "-")
|
|
316
|
+
.toLowerCase();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineExtension, type VerboseLevel } from "@spaceflow/core";
|
|
2
|
+
import { CreateService } from "./create.service";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create 命令扩展
|
|
6
|
+
*/
|
|
7
|
+
export const createExtension = defineExtension({
|
|
8
|
+
name: "create",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "创建 Extension",
|
|
11
|
+
commands: [
|
|
12
|
+
{
|
|
13
|
+
name: "create",
|
|
14
|
+
description: "创建 Extension",
|
|
15
|
+
arguments: "<name>",
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
flags: "-t, --template <template>",
|
|
19
|
+
description: "模板类型 (command, mcp, skills)",
|
|
20
|
+
default: "command",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
flags: "-v, --verbose",
|
|
24
|
+
description: "详细输出",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
run: async (args, options, ctx) => {
|
|
28
|
+
const name = args[0];
|
|
29
|
+
if (!name) {
|
|
30
|
+
ctx.output.error("请指定扩展名称");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const verbose = (options?.verbose ? 2 : 1) as VerboseLevel;
|
|
34
|
+
const createService = new CreateService();
|
|
35
|
+
const template = (options?.template as string) || "command";
|
|
36
|
+
await createService.createFromTemplate(template, name, {}, verbose);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export * from "./create.service";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineExtension, type VerboseLevel } from "@spaceflow/core";
|
|
2
|
+
import { BuildService } from "../build/build.service";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dev 命令扩展
|
|
6
|
+
*/
|
|
7
|
+
export const devExtension = defineExtension({
|
|
8
|
+
name: "dev",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "开发模式运行 Extension",
|
|
11
|
+
commands: [
|
|
12
|
+
{
|
|
13
|
+
name: "dev",
|
|
14
|
+
description: "开发模式(监听构建)",
|
|
15
|
+
arguments: "[extension]",
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
flags: "-v, --verbose",
|
|
19
|
+
description: "详细输出",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
run: async (args, options, _ctx) => {
|
|
23
|
+
const extensionName = args[0];
|
|
24
|
+
const verbose = (options?.verbose ? 2 : 1) as VerboseLevel;
|
|
25
|
+
const buildService = new BuildService();
|
|
26
|
+
await buildService.watch(extensionName, verbose);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defineExtension, SchemaGeneratorService, type VerboseLevel } from "@spaceflow/core";
|
|
2
|
+
import { InstallService } from "./install.service";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Install 命令扩展
|
|
6
|
+
*/
|
|
7
|
+
export const installExtension = defineExtension({
|
|
8
|
+
name: "install",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
description: "安装 Extension",
|
|
11
|
+
commands: [
|
|
12
|
+
{
|
|
13
|
+
name: "install",
|
|
14
|
+
description: "安装 Extension(无参数时安装配置文件中的所有依赖)",
|
|
15
|
+
aliases: ["i"],
|
|
16
|
+
arguments: "[source]",
|
|
17
|
+
options: [
|
|
18
|
+
{
|
|
19
|
+
flags: "-n, --name <name>",
|
|
20
|
+
description: "指定安装名称",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
flags: "-r, --ref <ref>",
|
|
24
|
+
description: "指定版本/分支/tag",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
flags: "-g, --global",
|
|
28
|
+
description: "全局安装",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
flags: "-v, --verbose",
|
|
32
|
+
description: "详细输出",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
run: async (args, options, ctx) => {
|
|
36
|
+
const source = args[0];
|
|
37
|
+
const verbose = (options?.verbose ? 2 : 1) as VerboseLevel;
|
|
38
|
+
const schemaGenerator = new SchemaGeneratorService();
|
|
39
|
+
const installService = new InstallService(schemaGenerator);
|
|
40
|
+
// 无参数时,安装配置文件中的所有依赖
|
|
41
|
+
if (!source) {
|
|
42
|
+
if (options?.global) {
|
|
43
|
+
ctx.output.error("全局安装需要指定 source 参数");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
await installService.updateAllExtensions({ verbose });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const installOptions = {
|
|
50
|
+
source,
|
|
51
|
+
name: options?.name as string,
|
|
52
|
+
ref: options?.ref as string,
|
|
53
|
+
};
|
|
54
|
+
if (options?.global) {
|
|
55
|
+
await installService.installGlobal(installOptions, verbose);
|
|
56
|
+
} else {
|
|
57
|
+
const context = installService.getContext(installOptions);
|
|
58
|
+
await installService.execute(context, verbose);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export * from "./install.service";
|