@devframes/service-git 0.9.8 → 0.9.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.d.mts +5 -5
- package/dist/index.mjs +65 -56
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -38,7 +38,7 @@ interface Commit {
|
|
|
38
38
|
body: string;
|
|
39
39
|
/** Ref names pointing at this commit (branches, tags, HEAD). */
|
|
40
40
|
refs: string[];
|
|
41
|
-
/** Full parent hashes
|
|
41
|
+
/** Full parent hashes; drives the commit graph. */
|
|
42
42
|
parents: string[];
|
|
43
43
|
}
|
|
44
44
|
interface GitLog {
|
|
@@ -80,7 +80,7 @@ interface Tag {
|
|
|
80
80
|
/** Short SHA of the commit the tag ultimately points to. */
|
|
81
81
|
sha: string;
|
|
82
82
|
/**
|
|
83
|
-
* Tag creation date as epoch milliseconds
|
|
83
|
+
* Tag creation date as epoch milliseconds: the tag's own date for
|
|
84
84
|
* annotated tags, the target commit's date for lightweight tags. `0` when
|
|
85
85
|
* the date can't be parsed.
|
|
86
86
|
*/
|
|
@@ -129,7 +129,7 @@ interface GitDiff {
|
|
|
129
129
|
files: DiffFile[];
|
|
130
130
|
totalAdditions: number;
|
|
131
131
|
totalDeletions: number;
|
|
132
|
-
/** Unified patch text
|
|
132
|
+
/** Unified patch text, populated when `path` targets a single file. */
|
|
133
133
|
patch: string | null;
|
|
134
134
|
/** `true` when `patch` was clipped to the internal char limit. */
|
|
135
135
|
truncated: boolean;
|
|
@@ -248,11 +248,11 @@ declare module 'devframe' {
|
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
/**
|
|
251
|
-
* The git wire service
|
|
251
|
+
* The git wire service: read/write git operations shared over RPC by every
|
|
252
252
|
* plugin on the host, generalizing the utilities that used to live inside the
|
|
253
253
|
* git plugin. The exec wrapper and output parsers stay internal; consumers get
|
|
254
254
|
* the typed {@link GitServiceApi} in-process (`ctx.services.get`) and the same
|
|
255
|
-
* ops over `devframes:service:git:*` RPC. Write ops are always exposed
|
|
255
|
+
* ops over `devframes:service:git:*` RPC. Write ops are always exposed;
|
|
256
256
|
* authorization is the host's connection-trust boundary. The service defines
|
|
257
257
|
* no `dump`/`snapshot`; a devframe bakes what it needs via `snapshotRpc`.
|
|
258
258
|
*/
|
package/dist/index.mjs
CHANGED
|
@@ -4,13 +4,13 @@ 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.10";
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/git.ts
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
const MAX_BUFFER = 67108864;
|
|
12
12
|
/**
|
|
13
|
-
* Run a git command in `cwd`. Rejects when git exits non-zero
|
|
13
|
+
* Run a git command in `cwd`. Rejects when git exits non-zero; callers that
|
|
14
14
|
* tolerate failure (e.g. "no upstream configured") should use {@link tryGit}.
|
|
15
15
|
*/
|
|
16
16
|
async function runGit(cwd, args) {
|
|
@@ -18,6 +18,7 @@ async function runGit(cwd, args) {
|
|
|
18
18
|
cwd,
|
|
19
19
|
maxBuffer: MAX_BUFFER,
|
|
20
20
|
windowsHide: true,
|
|
21
|
+
/** Force plain, locale-independent output so parsers stay stable. */
|
|
21
22
|
env: {
|
|
22
23
|
...process.env,
|
|
23
24
|
GIT_PAGER: "cat",
|
|
@@ -95,6 +96,56 @@ function mapCode(code) {
|
|
|
95
96
|
default: return "unknown";
|
|
96
97
|
}
|
|
97
98
|
}
|
|
99
|
+
function applyBranchToken(status, token) {
|
|
100
|
+
const [, key, ...rest] = token.split(" ");
|
|
101
|
+
const value = rest.join(" ");
|
|
102
|
+
switch (key) {
|
|
103
|
+
case "branch.head":
|
|
104
|
+
status.detached = value === "(detached)";
|
|
105
|
+
status.branch = status.detached ? null : value;
|
|
106
|
+
return;
|
|
107
|
+
case "branch.oid":
|
|
108
|
+
if (value !== "(initial)") status.head = value.slice(0, 9);
|
|
109
|
+
return;
|
|
110
|
+
case "branch.upstream":
|
|
111
|
+
status.upstream = value;
|
|
112
|
+
return;
|
|
113
|
+
case "branch.ab": {
|
|
114
|
+
const match = value.match(/\+(\d+)\s+-(\d+)/);
|
|
115
|
+
if (match) {
|
|
116
|
+
status.ahead = Number(match[1]);
|
|
117
|
+
status.behind = Number(match[2]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Record a tracked change (`1 ` ordinary or `2 ` rename/copy) and return the
|
|
124
|
+
* token index consumed - a rename eats the following NUL token for its origin.
|
|
125
|
+
* Type 1 path begins at field 8; type 2 inserts the rename score at field 8,
|
|
126
|
+
* pushing the path to field 9 and the original to that extra token.
|
|
127
|
+
*/
|
|
128
|
+
function applyTrackedEntry(status, tokens, index) {
|
|
129
|
+
const fields = tokens[index].split(" ");
|
|
130
|
+
const [x, y] = fields[1];
|
|
131
|
+
const renamed = tokens[index].startsWith("2 ");
|
|
132
|
+
const path = fields.slice(renamed ? 9 : 8).join(" ");
|
|
133
|
+
const consumed = renamed ? index + 1 : index;
|
|
134
|
+
const from = renamed ? tokens[consumed] : void 0;
|
|
135
|
+
if (x !== ".") status.staged.push(from ? {
|
|
136
|
+
path,
|
|
137
|
+
from,
|
|
138
|
+
status: mapCode(x)
|
|
139
|
+
} : {
|
|
140
|
+
path,
|
|
141
|
+
status: mapCode(x)
|
|
142
|
+
});
|
|
143
|
+
if (y !== ".") status.unstaged.push({
|
|
144
|
+
path,
|
|
145
|
+
status: mapCode(y)
|
|
146
|
+
});
|
|
147
|
+
return consumed;
|
|
148
|
+
}
|
|
98
149
|
/**
|
|
99
150
|
* Parse `git status --porcelain=v2 --branch -z` into a structured snapshot.
|
|
100
151
|
* Records are NUL-separated; rename/copy (type `2`) entries consume an extra
|
|
@@ -113,56 +164,13 @@ function parseStatus(root, raw) {
|
|
|
113
164
|
for (let i = 0; i < tokens.length; i++) {
|
|
114
165
|
const token = tokens[i];
|
|
115
166
|
if (!token) continue;
|
|
116
|
-
if (token.startsWith("# "))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
} else status.branch = value;
|
|
124
|
-
} else if (key === "branch.oid" && value !== "(initial)") status.head = value.slice(0, 9);
|
|
125
|
-
else if (key === "branch.upstream") status.upstream = value;
|
|
126
|
-
else if (key === "branch.ab") {
|
|
127
|
-
const match = value.match(/\+(\d+)\s+-(\d+)/);
|
|
128
|
-
if (match) {
|
|
129
|
-
status.ahead = Number(match[1]);
|
|
130
|
-
status.behind = Number(match[2]);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (token.startsWith("1 ") || token.startsWith("2 ")) {
|
|
136
|
-
const renamed = token.startsWith("2 ");
|
|
137
|
-
const fields = token.split(" ");
|
|
138
|
-
const xy = fields[1];
|
|
139
|
-
const x = xy[0];
|
|
140
|
-
const y = xy[1];
|
|
141
|
-
const path = fields.slice(renamed ? 9 : 8).join(" ");
|
|
142
|
-
const from = renamed ? tokens[++i] : void 0;
|
|
143
|
-
if (x !== ".") status.staged.push(from ? {
|
|
144
|
-
path,
|
|
145
|
-
from,
|
|
146
|
-
status: mapCode(x)
|
|
147
|
-
} : {
|
|
148
|
-
path,
|
|
149
|
-
status: mapCode(x)
|
|
150
|
-
});
|
|
151
|
-
if (y !== ".") status.unstaged.push({
|
|
152
|
-
path,
|
|
153
|
-
status: mapCode(y)
|
|
154
|
-
});
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (token.startsWith("u ")) {
|
|
158
|
-
const path = token.split(" ").slice(10).join(" ");
|
|
159
|
-
status.unstaged.push({
|
|
160
|
-
path,
|
|
161
|
-
status: "unmerged"
|
|
162
|
-
});
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (token.startsWith("? ")) status.untracked.push(token.slice(2));
|
|
167
|
+
if (token.startsWith("# ")) applyBranchToken(status, token);
|
|
168
|
+
else if (token.startsWith("1 ") || token.startsWith("2 ")) i = applyTrackedEntry(status, tokens, i);
|
|
169
|
+
else if (token.startsWith("u ")) status.unstaged.push({
|
|
170
|
+
path: token.split(" ").slice(10).join(" "),
|
|
171
|
+
status: "unmerged"
|
|
172
|
+
});
|
|
173
|
+
else if (token.startsWith("? ")) status.untracked.push(token.slice(2));
|
|
166
174
|
}
|
|
167
175
|
status.clean = status.staged.length === 0 && status.unstaged.length === 0 && status.untracked.length === 0;
|
|
168
176
|
return status;
|
|
@@ -754,11 +762,11 @@ const gitDiffSchema = s.object({
|
|
|
754
762
|
truncated: s.boolean()
|
|
755
763
|
});
|
|
756
764
|
/**
|
|
757
|
-
* The git wire service
|
|
765
|
+
* The git wire service: read/write git operations shared over RPC by every
|
|
758
766
|
* plugin on the host, generalizing the utilities that used to live inside the
|
|
759
767
|
* git plugin. The exec wrapper and output parsers stay internal; consumers get
|
|
760
768
|
* the typed {@link GitServiceApi} in-process (`ctx.services.get`) and the same
|
|
761
|
-
* ops over `devframes:service:git:*` RPC. Write ops are always exposed
|
|
769
|
+
* ops over `devframes:service:git:*` RPC. Write ops are always exposed;
|
|
762
770
|
* authorization is the host's connection-trust boundary. The service defines
|
|
763
771
|
* no `dump`/`snapshot`; a devframe bakes what it needs via `snapshotRpc`.
|
|
764
772
|
*/
|
|
@@ -768,6 +776,7 @@ function createGitService(options) {
|
|
|
768
776
|
version,
|
|
769
777
|
scope: GIT_SERVICE_SCOPE,
|
|
770
778
|
options,
|
|
779
|
+
/** `cwd` deep-merges as a scalar (later installer wins) across declarers. */
|
|
771
780
|
setup(ctx, { options }) {
|
|
772
781
|
const ops = createGitOps(options?.cwd ?? ctx.cwd);
|
|
773
782
|
ctx.rpc.register(defineRpcFunction({
|
|
@@ -823,7 +832,7 @@ function createGitService(options) {
|
|
|
823
832
|
returns: gitFileSchema,
|
|
824
833
|
agent: {
|
|
825
834
|
title: "Git read file",
|
|
826
|
-
description: "Read the contents of a single file at a commit-ish (default HEAD)
|
|
835
|
+
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
836
|
},
|
|
828
837
|
handler: (args) => ops.readFile(args)
|
|
829
838
|
}));
|
|
@@ -838,7 +847,7 @@ function createGitService(options) {
|
|
|
838
847
|
returns: gitDiffSchema,
|
|
839
848
|
agent: {
|
|
840
849
|
title: "Git diff",
|
|
841
|
-
description: "Unified diff of uncommitted changes
|
|
850
|
+
description: "Unified diff of uncommitted changes: the working tree by default, the index with staged: true, one file with path. Safe to call freely."
|
|
842
851
|
},
|
|
843
852
|
handler: (args = {}) => ops.diff(args)
|
|
844
853
|
}));
|
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.10",
|
|
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,11 +28,11 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"devframe": "0.9.
|
|
31
|
+
"devframe": "0.9.10"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^26.4.0",
|
|
35
|
-
"devframe": "0.9.
|
|
35
|
+
"devframe": "0.9.10",
|
|
36
36
|
"tsdown": "^0.22.14",
|
|
37
37
|
"vitest": "^4.1.11"
|
|
38
38
|
},
|