@devframes/service-git 0.9.7 → 0.9.9

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 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 — drives the commit graph. */
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 — the tag's own date for
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 — populated when `path` targets a single file. */
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 — read/write git operations shared over RPC by every
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";
7
+ var version = "0.9.9";
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 — callers that
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
- const [, key, ...rest] = token.split(" ");
118
- const value = rest.join(" ");
119
- if (key === "branch.head") {
120
- if (value === "(detached)") {
121
- status.detached = true;
122
- status.branch = null;
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 — read/write git operations shared over RPC by every
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) — 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."
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 — the working tree by default, the index with staged: true, one file with path. Safe to call freely."
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.7",
4
+ "version": "0.9.9",
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.7"
31
+ "devframe": "0.9.9"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^26.2.0",
34
+ "@types/node": "^26.4.0",
35
+ "devframe": "0.9.9",
35
36
  "tsdown": "^0.22.14",
36
- "vitest": "^4.1.10",
37
- "devframe": "0.9.7"
37
+ "vitest": "^4.1.11"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsdown",