@devframes/service-git 0.9.3 → 0.9.5
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.d.mts +48 -1
- package/dist/index.mjs +131 -7
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -56,6 +56,8 @@ interface LogArgs {
|
|
|
56
56
|
skip?: number;
|
|
57
57
|
/** Optional ref/branch to read history from (default: current HEAD). */
|
|
58
58
|
ref?: string;
|
|
59
|
+
/** Restrict history to commits that touched these repo-relative path(s). */
|
|
60
|
+
paths?: string[];
|
|
59
61
|
}
|
|
60
62
|
interface Branch {
|
|
61
63
|
name: string;
|
|
@@ -73,6 +75,47 @@ interface GitBranches {
|
|
|
73
75
|
current: string | null;
|
|
74
76
|
branches: Branch[];
|
|
75
77
|
}
|
|
78
|
+
interface Tag {
|
|
79
|
+
name: string;
|
|
80
|
+
/** Short SHA of the commit the tag ultimately points to. */
|
|
81
|
+
sha: string;
|
|
82
|
+
/**
|
|
83
|
+
* Tag creation date as epoch milliseconds — the tag's own date for
|
|
84
|
+
* annotated tags, the target commit's date for lightweight tags. `0` when
|
|
85
|
+
* the date can't be parsed.
|
|
86
|
+
*/
|
|
87
|
+
date: number;
|
|
88
|
+
/** Tag message subject (annotated) or target commit subject (lightweight). */
|
|
89
|
+
subject: string;
|
|
90
|
+
/** `true` for annotated tags, which carry their own message and date. */
|
|
91
|
+
annotated: boolean;
|
|
92
|
+
}
|
|
93
|
+
interface GitTags {
|
|
94
|
+
isRepo: boolean;
|
|
95
|
+
/** Tags, newest creation date first. */
|
|
96
|
+
tags: Tag[];
|
|
97
|
+
}
|
|
98
|
+
interface ReadFileArgs {
|
|
99
|
+
/** Repo-relative path to the file. */
|
|
100
|
+
path: string;
|
|
101
|
+
/** Commit-ish to read the file from (default: current HEAD). */
|
|
102
|
+
ref?: string;
|
|
103
|
+
}
|
|
104
|
+
interface GitFile {
|
|
105
|
+
/** `false` when the working directory is not inside a git repository. */
|
|
106
|
+
isRepo: boolean;
|
|
107
|
+
/** `false` when no blob exists at `path` for `ref`. */
|
|
108
|
+
found: boolean;
|
|
109
|
+
/** The resolved ref the file was read from. */
|
|
110
|
+
ref: string;
|
|
111
|
+
path: string;
|
|
112
|
+
/** File text, or `null` when not found or binary. */
|
|
113
|
+
content: string | null;
|
|
114
|
+
/** `true` when the blob is binary (its `content` is omitted). */
|
|
115
|
+
binary: boolean;
|
|
116
|
+
/** `true` when `content` was clipped to the internal char limit. */
|
|
117
|
+
truncated: boolean;
|
|
118
|
+
}
|
|
76
119
|
interface DiffFile {
|
|
77
120
|
path: string;
|
|
78
121
|
additions: number;
|
|
@@ -165,8 +208,10 @@ interface GitServiceApi {
|
|
|
165
208
|
status: () => Promise<GitStatus>;
|
|
166
209
|
log: (args?: LogArgs) => Promise<GitLog>;
|
|
167
210
|
show: (args: ShowArgs) => Promise<CommitDetail>;
|
|
211
|
+
readFile: (args: ReadFileArgs) => Promise<GitFile>;
|
|
168
212
|
diff: (args?: DiffArgs) => Promise<GitDiff>;
|
|
169
213
|
branches: () => Promise<GitBranches>;
|
|
214
|
+
tags: () => Promise<GitTags>;
|
|
170
215
|
stage: (args: StageArgs) => Promise<GitStatus>;
|
|
171
216
|
unstage: (args: UnstageArgs) => Promise<GitStatus>;
|
|
172
217
|
commit: (args: CommitArgs) => Promise<CommitResult>;
|
|
@@ -187,8 +232,10 @@ declare module 'devframe' {
|
|
|
187
232
|
'devframes:service:git:status': () => Promise<GitStatus>;
|
|
188
233
|
'devframes:service:git:log': (args?: LogArgs) => Promise<GitLog>;
|
|
189
234
|
'devframes:service:git:show': (args: ShowArgs) => Promise<CommitDetail>;
|
|
235
|
+
'devframes:service:git:readFile': (args: ReadFileArgs) => Promise<GitFile>;
|
|
190
236
|
'devframes:service:git:diff': (args?: DiffArgs) => Promise<GitDiff>;
|
|
191
237
|
'devframes:service:git:branches': () => Promise<GitBranches>;
|
|
238
|
+
'devframes:service:git:tags': () => Promise<GitTags>;
|
|
192
239
|
'devframes:service:git:stage': (args: StageArgs) => Promise<GitStatus>;
|
|
193
240
|
'devframes:service:git:unstage': (args: UnstageArgs) => Promise<GitStatus>;
|
|
194
241
|
'devframes:service:git:commit': (args: CommitArgs) => Promise<CommitResult>;
|
|
@@ -211,4 +258,4 @@ declare module 'devframe' {
|
|
|
211
258
|
*/
|
|
212
259
|
declare function createGitService(options?: GitServiceOptions): DevframeServiceDefinition<GitServiceApi, GitServiceOptions>;
|
|
213
260
|
//#endregion
|
|
214
|
-
export { Branch, Commit, CommitArgs, CommitDetail, CommitFile, CommitResult, DiffArgs, DiffFile, FileStatusCode, GIT_SERVICE_PACKAGE, GIT_SERVICE_SCOPE, GitBranches, GitDiff, GitLog, GitServiceApi, GitServiceOptions, GitStatus, LogArgs, ShowArgs, StageArgs, StatusFileEntry, UnstageArgs, createGitService, createGitService as default };
|
|
261
|
+
export { Branch, Commit, CommitArgs, CommitDetail, CommitFile, CommitResult, DiffArgs, DiffFile, FileStatusCode, GIT_SERVICE_PACKAGE, GIT_SERVICE_SCOPE, GitBranches, GitDiff, GitFile, GitLog, GitServiceApi, GitServiceOptions, GitStatus, GitTags, LogArgs, ReadFileArgs, ShowArgs, StageArgs, StatusFileEntry, Tag, UnstageArgs, createGitService, createGitService as default };
|
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { execFile } from "node:child_process";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
//#region package.json
|
|
7
|
-
var version = "0.9.
|
|
7
|
+
var version = "0.9.5";
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/git.ts
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -66,6 +66,8 @@ function gitErrorMessage(error) {
|
|
|
66
66
|
//#region src/operations.ts
|
|
67
67
|
/** Hard cap on returned patch text to keep payloads bounded. */
|
|
68
68
|
const PATCH_CHAR_LIMIT = 2e5;
|
|
69
|
+
/** Hard cap on returned raw file content to keep payloads bounded. */
|
|
70
|
+
const FILE_CHAR_LIMIT = 5e5;
|
|
69
71
|
const EMPTY_STATUS = {
|
|
70
72
|
isRepo: false,
|
|
71
73
|
root: null,
|
|
@@ -203,6 +205,14 @@ const BRANCH_FORMAT = [
|
|
|
203
205
|
"%(upstream:track)",
|
|
204
206
|
"%(contents:subject)"
|
|
205
207
|
].join("");
|
|
208
|
+
const TAG_FORMAT = [
|
|
209
|
+
"%(refname:short)",
|
|
210
|
+
"%(objecttype)",
|
|
211
|
+
"%(objectname:short)",
|
|
212
|
+
"%(*objectname:short)",
|
|
213
|
+
"%(creatordate:iso-strict)",
|
|
214
|
+
"%(contents:subject)"
|
|
215
|
+
].join("");
|
|
206
216
|
function parseTrack(track) {
|
|
207
217
|
if (track.includes("gone")) return {
|
|
208
218
|
ahead: 0,
|
|
@@ -299,15 +309,22 @@ function parseCommitNumstat(raw, status) {
|
|
|
299
309
|
};
|
|
300
310
|
});
|
|
301
311
|
}
|
|
302
|
-
function
|
|
303
|
-
return raw.length >
|
|
304
|
-
|
|
312
|
+
function clipText(raw, limit) {
|
|
313
|
+
return raw.length > limit ? {
|
|
314
|
+
text: raw.slice(0, limit),
|
|
305
315
|
truncated: true
|
|
306
316
|
} : {
|
|
307
|
-
|
|
317
|
+
text: raw,
|
|
308
318
|
truncated: false
|
|
309
319
|
};
|
|
310
320
|
}
|
|
321
|
+
function clipPatch(raw) {
|
|
322
|
+
const { text, truncated } = clipText(raw, PATCH_CHAR_LIMIT);
|
|
323
|
+
return {
|
|
324
|
+
patch: text,
|
|
325
|
+
truncated
|
|
326
|
+
};
|
|
327
|
+
}
|
|
311
328
|
/**
|
|
312
329
|
* Build the git node API bound to a single working directory, with the repo
|
|
313
330
|
* root resolved once (memoized). This is the surface returned to in-process
|
|
@@ -431,6 +448,8 @@ function createGitOps(cwd) {
|
|
|
431
448
|
};
|
|
432
449
|
command.push("--end-of-options", ref);
|
|
433
450
|
}
|
|
451
|
+
const paths = (args.paths ?? []).map((p) => p.trim()).filter(Boolean);
|
|
452
|
+
if (paths.length > 0) command.push("--", ...paths);
|
|
434
453
|
const raw = await tryGit(cwd, command);
|
|
435
454
|
const commits = raw ? parseLog(raw) : [];
|
|
436
455
|
return {
|
|
@@ -447,6 +466,44 @@ function createGitOps(cwd) {
|
|
|
447
466
|
if (!await resolveRoot() || !hash) return { ...EMPTY_DETAIL };
|
|
448
467
|
return readCommit(hash, includePatch);
|
|
449
468
|
},
|
|
469
|
+
async readFile(args) {
|
|
470
|
+
const path = (args?.path ?? "").trim();
|
|
471
|
+
const ref = args?.ref?.trim() || "HEAD";
|
|
472
|
+
const root = await resolveRoot();
|
|
473
|
+
const base = {
|
|
474
|
+
isRepo: !!root,
|
|
475
|
+
found: false,
|
|
476
|
+
ref,
|
|
477
|
+
path,
|
|
478
|
+
content: null,
|
|
479
|
+
binary: false,
|
|
480
|
+
truncated: false
|
|
481
|
+
};
|
|
482
|
+
if (!root || !path) return base;
|
|
483
|
+
if (!isSafeRevision(ref)) return base;
|
|
484
|
+
let raw;
|
|
485
|
+
try {
|
|
486
|
+
({stdout: raw} = await runGit(cwd, [
|
|
487
|
+
"show",
|
|
488
|
+
"--end-of-options",
|
|
489
|
+
`${ref}:${path}`
|
|
490
|
+
]));
|
|
491
|
+
} catch {
|
|
492
|
+
return base;
|
|
493
|
+
}
|
|
494
|
+
if (raw.includes("\0")) return {
|
|
495
|
+
...base,
|
|
496
|
+
found: true,
|
|
497
|
+
binary: true
|
|
498
|
+
};
|
|
499
|
+
const { text: content, truncated } = clipText(raw, FILE_CHAR_LIMIT);
|
|
500
|
+
return {
|
|
501
|
+
...base,
|
|
502
|
+
found: true,
|
|
503
|
+
content,
|
|
504
|
+
truncated
|
|
505
|
+
};
|
|
506
|
+
},
|
|
450
507
|
async diff(args = {}) {
|
|
451
508
|
const { path, staged = false } = args;
|
|
452
509
|
if (!await resolveRoot()) return {
|
|
@@ -521,6 +578,38 @@ function createGitOps(cwd) {
|
|
|
521
578
|
branches
|
|
522
579
|
};
|
|
523
580
|
},
|
|
581
|
+
async tags() {
|
|
582
|
+
if (!await resolveRoot()) return {
|
|
583
|
+
isRepo: false,
|
|
584
|
+
tags: []
|
|
585
|
+
};
|
|
586
|
+
const raw = await tryGit(cwd, [
|
|
587
|
+
"for-each-ref",
|
|
588
|
+
`--format=${TAG_FORMAT}`,
|
|
589
|
+
"refs/tags"
|
|
590
|
+
]);
|
|
591
|
+
if (!raw) return {
|
|
592
|
+
isRepo: true,
|
|
593
|
+
tags: []
|
|
594
|
+
};
|
|
595
|
+
const tags = splitClean(raw, "\n").map((line) => {
|
|
596
|
+
const [name, objectType, objectSha, targetSha, isoDate, subject] = line.split("");
|
|
597
|
+
const annotated = objectType === "tag";
|
|
598
|
+
const parsed = Date.parse(isoDate);
|
|
599
|
+
return {
|
|
600
|
+
name,
|
|
601
|
+
sha: targetSha || objectSha,
|
|
602
|
+
date: Number.isNaN(parsed) ? 0 : parsed,
|
|
603
|
+
subject: subject ?? "",
|
|
604
|
+
annotated
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
tags.sort((a, b) => b.date - a.date);
|
|
608
|
+
return {
|
|
609
|
+
isRepo: true,
|
|
610
|
+
tags
|
|
611
|
+
};
|
|
612
|
+
},
|
|
524
613
|
async stage(args) {
|
|
525
614
|
const paths = args?.paths ?? [];
|
|
526
615
|
if (await resolveRoot() && paths.length > 0) await runGit(cwd, [
|
|
@@ -640,6 +729,15 @@ const commitDetailSchema = s.object({
|
|
|
640
729
|
patch: s.nullable(s.string()),
|
|
641
730
|
truncated: s.boolean()
|
|
642
731
|
});
|
|
732
|
+
const gitFileSchema = s.object({
|
|
733
|
+
isRepo: s.boolean(),
|
|
734
|
+
found: s.boolean(),
|
|
735
|
+
ref: s.string(),
|
|
736
|
+
path: s.string(),
|
|
737
|
+
content: s.nullable(s.string()),
|
|
738
|
+
binary: s.boolean(),
|
|
739
|
+
truncated: s.boolean()
|
|
740
|
+
});
|
|
643
741
|
const gitDiffSchema = s.object({
|
|
644
742
|
isRepo: s.boolean(),
|
|
645
743
|
staged: s.boolean(),
|
|
@@ -689,12 +787,13 @@ function createGitService(options) {
|
|
|
689
787
|
args: [s.object({
|
|
690
788
|
limit: s.optional(s.number()),
|
|
691
789
|
skip: s.optional(s.number()),
|
|
692
|
-
ref: s.optional(s.string())
|
|
790
|
+
ref: s.optional(s.string()),
|
|
791
|
+
paths: s.optional(s.array(s.string()))
|
|
693
792
|
})],
|
|
694
793
|
returns: gitLogSchema,
|
|
695
794
|
agent: {
|
|
696
795
|
title: "Git log",
|
|
697
|
-
description: "Commit history of the inspected repository, newest first. Paginate with limit (1-200, default 30) and skip; pass ref to read another branch. Safe to call freely."
|
|
796
|
+
description: "Commit history of the inspected repository, newest first. Paginate with limit (1-200, default 30) and skip; pass ref to read another branch, or paths to list only commits that touched those files/directories. Safe to call freely."
|
|
698
797
|
},
|
|
699
798
|
handler: (args = {}) => ops.log(args)
|
|
700
799
|
}));
|
|
@@ -713,6 +812,21 @@ function createGitService(options) {
|
|
|
713
812
|
},
|
|
714
813
|
handler: (args) => ops.show(args)
|
|
715
814
|
}));
|
|
815
|
+
ctx.rpc.register(defineRpcFunction({
|
|
816
|
+
name: "readFile",
|
|
817
|
+
type: "query",
|
|
818
|
+
jsonSerializable: true,
|
|
819
|
+
args: [s.object({
|
|
820
|
+
path: s.string(),
|
|
821
|
+
ref: s.optional(s.string())
|
|
822
|
+
})],
|
|
823
|
+
returns: gitFileSchema,
|
|
824
|
+
agent: {
|
|
825
|
+
title: "Git read file",
|
|
826
|
+
description: "Read the contents of a single file at a commit-ish (default HEAD) — the raw text of a versioned file without checking it out. found is false when no such file exists at the ref; binary blobs return with content omitted. Safe to call freely."
|
|
827
|
+
},
|
|
828
|
+
handler: (args) => ops.readFile(args)
|
|
829
|
+
}));
|
|
716
830
|
ctx.rpc.register(defineRpcFunction({
|
|
717
831
|
name: "diff",
|
|
718
832
|
type: "query",
|
|
@@ -738,6 +852,16 @@ function createGitService(options) {
|
|
|
738
852
|
},
|
|
739
853
|
handler: () => ops.branches()
|
|
740
854
|
}));
|
|
855
|
+
ctx.rpc.register(defineRpcFunction({
|
|
856
|
+
name: "tags",
|
|
857
|
+
type: "query",
|
|
858
|
+
jsonSerializable: true,
|
|
859
|
+
agent: {
|
|
860
|
+
title: "Git tags",
|
|
861
|
+
description: "List tags (newest first) with the target commit SHA, creation date, and message subject; annotated tags are flagged. Safe to call freely."
|
|
862
|
+
},
|
|
863
|
+
handler: () => ops.tags()
|
|
864
|
+
}));
|
|
741
865
|
ctx.rpc.register(defineRpcFunction({
|
|
742
866
|
name: "stage",
|
|
743
867
|
type: "action",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devframes/service-git",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.5",
|
|
5
5
|
"description": "Devframe wire service exposing read/write git operations (status, log, show, diff, branches, stage, unstage, commit) over RPC.",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"devframe": "0.9.
|
|
31
|
+
"devframe": "0.9.5"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^26.2.0",
|
|
35
35
|
"tsdown": "^0.22.14",
|
|
36
36
|
"vitest": "^4.1.10",
|
|
37
|
-
"devframe": "0.9.
|
|
37
|
+
"devframe": "0.9.5"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsdown",
|