@spaceflow/core 0.23.0 → 0.25.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/dist/index.js +190 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/commands/install/install.service.ts +26 -3
- package/src/locales/en/install.json +1 -0
- package/src/locales/zh-cn/install.json +1 -0
- package/src/shared/git-sdk/git-sdk.service.ts +157 -0
- package/src/shared/git-sdk/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spaceflow/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Spaceflow 核心能力库",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Lydanne",
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"zod-to-json-schema": "^3.25.1",
|
|
100
100
|
"commander": "^12.1.0",
|
|
101
101
|
"i18next": "^25.8.4",
|
|
102
|
-
"@spaceflow/shared": "0.
|
|
102
|
+
"@spaceflow/shared": "0.7.0"
|
|
103
103
|
},
|
|
104
104
|
"devDependencies": {
|
|
105
105
|
"@swc/core": "1.15.3",
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getPackageManager,
|
|
17
17
|
detectPackageManager,
|
|
18
18
|
getSpaceflowDir,
|
|
19
|
+
getSpaceflowCoreVersion,
|
|
19
20
|
ensureSpaceflowPackageJson,
|
|
20
21
|
ensureEditorGitignore,
|
|
21
22
|
SchemaGeneratorService,
|
|
@@ -664,10 +665,32 @@ export class InstallService {
|
|
|
664
665
|
if (shouldLog(verbose, 1))
|
|
665
666
|
console.log(t("install:foundDeps", { count: Object.keys(dependencies).length }));
|
|
666
667
|
|
|
667
|
-
// 1.
|
|
668
|
+
// 1. 记录更新前的 @spaceflow/core 版本
|
|
669
|
+
const spaceflowPkgPath = join(spaceflowDir, "package.json");
|
|
670
|
+
let prevCoreVersion: string | undefined;
|
|
671
|
+
if (existsSync(spaceflowPkgPath)) {
|
|
672
|
+
try {
|
|
673
|
+
const prevPkg = JSON.parse(await readFile(spaceflowPkgPath, "utf-8"));
|
|
674
|
+
prevCoreVersion = prevPkg.dependencies?.["@spaceflow/core"];
|
|
675
|
+
} catch {
|
|
676
|
+
// ignore
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// 2. 更新 .spaceflow/package.json 中的所有依赖
|
|
668
681
|
await this.updateSpaceflowPackageJson(dependencies, spaceflowDir, verbose);
|
|
669
682
|
|
|
670
|
-
//
|
|
683
|
+
// 3. 版本变更提示
|
|
684
|
+
const currentCoreVersion = getSpaceflowCoreVersion();
|
|
685
|
+
if (prevCoreVersion && prevCoreVersion !== currentCoreVersion) {
|
|
686
|
+
if (shouldLog(verbose, 1)) {
|
|
687
|
+
console.log(
|
|
688
|
+
t("install:coreVersionChanged", { prev: prevCoreVersion, current: currentCoreVersion }),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// 4. 安装所有依赖
|
|
671
694
|
if (shouldLog(verbose, 1)) console.log(t("install:installingDeps"));
|
|
672
695
|
const pm = detectPackageManager(spaceflowDir);
|
|
673
696
|
try {
|
|
@@ -676,7 +699,7 @@ export class InstallService {
|
|
|
676
699
|
console.warn(t("install:pmInstallFailed", { pm }));
|
|
677
700
|
}
|
|
678
701
|
|
|
679
|
-
//
|
|
702
|
+
// 5. 处理每个依赖的 skills/commands 关联
|
|
680
703
|
for (const [name, config] of Object.entries(dependencies)) {
|
|
681
704
|
const { source } = this.parseExtensionConfig(config);
|
|
682
705
|
const sourceType = getSourceType(source);
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"updatingAll": "🔄 Updating all dependencies...",
|
|
37
37
|
"noDeps": " No dependencies in config file",
|
|
38
38
|
"foundDeps": " Found {{count}} dependencies",
|
|
39
|
+
"coreVersionChanged": "📦 @spaceflow/core version changed: {{prev}} → {{current}}",
|
|
39
40
|
"installingDeps": "\n📦 Installing dependencies...",
|
|
40
41
|
"pmInstallFailed": " Warning: {{pm}} install failed",
|
|
41
42
|
"depNotInstalled": " ⚠️ {{name}} not installed successfully, skipping",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"updatingAll": "🔄 更新所有依赖...",
|
|
37
37
|
"noDeps": " 配置文件中没有 dependencies",
|
|
38
38
|
"foundDeps": " 找到 {{count}} 个依赖",
|
|
39
|
+
"coreVersionChanged": "📦 @spaceflow/core 版本变更: {{prev}} → {{current}}",
|
|
39
40
|
"installingDeps": "\n📦 安装依赖...",
|
|
40
41
|
"pmInstallFailed": " 警告: {{pm}} install 失败",
|
|
41
42
|
"depNotInstalled": " ⚠️ {{name}} 未安装成功,跳过",
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { spawn, execSync } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
2
4
|
import type { GitCommit, GitChangedFile, GitDiffFile, GitRunOptions } from "./git-sdk.types";
|
|
3
5
|
import { mapGitStatus, parseDiffText } from "./git-sdk-diff.utils";
|
|
4
6
|
|
|
7
|
+
/** 本地代码审查模式 */
|
|
8
|
+
export type LocalReviewMode = "uncommitted" | "staged" | false;
|
|
9
|
+
|
|
5
10
|
export class GitSdkService {
|
|
6
11
|
protected readonly defaultOptions: GitRunOptions = {
|
|
7
12
|
cwd: process.cwd(),
|
|
@@ -183,6 +188,17 @@ export class GitSdkService {
|
|
|
183
188
|
return this.runCommand(["show", `${ref}:${filename}`], options);
|
|
184
189
|
}
|
|
185
190
|
|
|
191
|
+
/**
|
|
192
|
+
* 获取工作区文件的当前内容(包含未提交的修改)
|
|
193
|
+
* 直接读取文件系统,而不是从 git 获取
|
|
194
|
+
* @throws 如果文件不存在或无法读取
|
|
195
|
+
*/
|
|
196
|
+
getWorkingFileContent(filename: string, options?: GitRunOptions): string {
|
|
197
|
+
const cwd = options?.cwd || process.cwd();
|
|
198
|
+
const filepath = path.join(cwd, filename);
|
|
199
|
+
return fs.readFileSync(filepath, "utf-8");
|
|
200
|
+
}
|
|
201
|
+
|
|
186
202
|
getCommitDiff(sha: string, options?: GitRunOptions): GitDiffFile[] {
|
|
187
203
|
try {
|
|
188
204
|
const output = this.runCommandSync(["show", "--format=", "--patch", sha], options);
|
|
@@ -193,6 +209,147 @@ export class GitSdkService {
|
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 获取暂存区的变更文件列表
|
|
214
|
+
*/
|
|
215
|
+
getStagedFiles(options?: GitRunOptions): GitChangedFile[] {
|
|
216
|
+
try {
|
|
217
|
+
const output = this.runCommandSync(["diff", "--cached", "--name-status"], options);
|
|
218
|
+
return this.parseNameStatusOutput(output);
|
|
219
|
+
} catch {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 获取暂存区的 diff(包含 patch 信息)
|
|
226
|
+
*/
|
|
227
|
+
getStagedDiff(options?: GitRunOptions): GitDiffFile[] {
|
|
228
|
+
try {
|
|
229
|
+
const output = this.runCommandSync(["diff", "--cached"], options);
|
|
230
|
+
return parseDiffText(output);
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 获取工作区未暂存的变更文件列表
|
|
238
|
+
*/
|
|
239
|
+
getUnstagedFiles(options?: GitRunOptions): GitChangedFile[] {
|
|
240
|
+
try {
|
|
241
|
+
const output = this.runCommandSync(["diff", "--name-status"], options);
|
|
242
|
+
return this.parseNameStatusOutput(output);
|
|
243
|
+
} catch {
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 获取工作区未暂存的 diff(包含 patch 信息)
|
|
250
|
+
*/
|
|
251
|
+
getUnstagedDiff(options?: GitRunOptions): GitDiffFile[] {
|
|
252
|
+
try {
|
|
253
|
+
const output = this.runCommandSync(["diff"], options);
|
|
254
|
+
return parseDiffText(output);
|
|
255
|
+
} catch {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 获取所有未提交的变更文件(暂存区 + 工作区 + untracked)
|
|
262
|
+
* 注意:staged 状态优先于 unstaged(因为 staged 反映了文件的原始变更类型)
|
|
263
|
+
*/
|
|
264
|
+
getUncommittedFiles(options?: GitRunOptions): GitChangedFile[] {
|
|
265
|
+
const staged = this.getStagedFiles(options);
|
|
266
|
+
const unstaged = this.getUnstagedFiles(options);
|
|
267
|
+
const untracked = this.getUntrackedFiles(options);
|
|
268
|
+
|
|
269
|
+
// staged 优先:如果文件在 staged 中是 added,即使 unstaged 中是 modified,也应该保持 added
|
|
270
|
+
const fileMap = new Map<string, GitChangedFile>();
|
|
271
|
+
for (const file of [...unstaged, ...staged]) {
|
|
272
|
+
fileMap.set(file.filename, file);
|
|
273
|
+
}
|
|
274
|
+
// 添加 untracked 文件
|
|
275
|
+
for (const filename of untracked) {
|
|
276
|
+
if (!fileMap.has(filename)) {
|
|
277
|
+
fileMap.set(filename, { filename, status: "added" });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return Array.from(fileMap.values());
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 获取未跟踪的文件列表
|
|
285
|
+
*/
|
|
286
|
+
getUntrackedFiles(options?: GitRunOptions): string[] {
|
|
287
|
+
try {
|
|
288
|
+
const output = this.runCommandSync(["ls-files", "--others", "--exclude-standard"], options);
|
|
289
|
+
return output.trim().split("\n").filter(Boolean);
|
|
290
|
+
} catch {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* 获取所有未提交的 diff(暂存区 + 工作区 + untracked)
|
|
297
|
+
* 使用 HEAD 作为基准比较,untracked 文件生成伪 patch
|
|
298
|
+
*/
|
|
299
|
+
getUncommittedDiff(options?: GitRunOptions): GitDiffFile[] {
|
|
300
|
+
const diffs: GitDiffFile[] = [];
|
|
301
|
+
|
|
302
|
+
// 1. 获取已跟踪文件的 diff(staged + unstaged)
|
|
303
|
+
try {
|
|
304
|
+
const output = this.runCommandSync(["diff", "HEAD"], options);
|
|
305
|
+
diffs.push(...parseDiffText(output));
|
|
306
|
+
} catch {
|
|
307
|
+
// ignore
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 2. 为 untracked 文件生成伪 patch
|
|
311
|
+
const untrackedFiles = this.getUntrackedFiles(options);
|
|
312
|
+
for (const filename of untrackedFiles) {
|
|
313
|
+
try {
|
|
314
|
+
const content = this.getWorkingFileContent(filename, options);
|
|
315
|
+
const patch = this.generateAddedFilePatch(content);
|
|
316
|
+
diffs.push({ filename, patch });
|
|
317
|
+
} catch {
|
|
318
|
+
// 文件读取失败,跳过
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return diffs;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 为新增文件生成伪 patch(所有行都是新增)
|
|
327
|
+
*/
|
|
328
|
+
protected generateAddedFilePatch(content: string): string {
|
|
329
|
+
const lines = content.split("\n");
|
|
330
|
+
const lineCount = lines.length;
|
|
331
|
+
const patchLines = lines.map((line) => `+${line}`);
|
|
332
|
+
return `@@ -0,0 +1,${lineCount} @@\n${patchLines.join("\n")}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 解析 git diff --name-status 输出
|
|
337
|
+
*/
|
|
338
|
+
protected parseNameStatusOutput(output: string): GitChangedFile[] {
|
|
339
|
+
const files: GitChangedFile[] = [];
|
|
340
|
+
const lines = output.trim().split("\n").filter(Boolean);
|
|
341
|
+
for (const line of lines) {
|
|
342
|
+
const [status, filename] = line.split("\t");
|
|
343
|
+
if (filename) {
|
|
344
|
+
files.push({
|
|
345
|
+
filename,
|
|
346
|
+
status: mapGitStatus(status),
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return files;
|
|
351
|
+
}
|
|
352
|
+
|
|
196
353
|
/**
|
|
197
354
|
* 解析 ref,支持本地分支、远程分支、commit SHA
|
|
198
355
|
* 优先级:commit SHA > 本地分支 > origin/分支 > fetch后重试 > 原始值
|