diffact 0.1.0 → 0.2.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/cli.mjs +2 -0
- package/dist/index-node-bKTmbwGt.mjs +1 -0
- package/dist/src-CPKE75x0.mjs +11 -0
- package/dist/src-Ceryd8j5.mjs +1 -0
- package/package.json +4 -5
- package/web/dist/assets/{code-block-37QAKDTI-C97XC0lL.js → code-block-37QAKDTI-yDNOZoY4.js} +1 -1
- package/web/dist/assets/{index-DWCfDth4.js → index-BlaXWu6U.js} +30 -30
- package/web/dist/assets/index-DMEToi1s.css +1 -0
- package/web/dist/index.html +2 -2
- package/dist/agent-manager.d.ts +0 -32
- package/dist/agent-manager.js +0 -502
- package/dist/app-server-client.d.ts +0 -38
- package/dist/app-server-client.js +0 -249
- package/dist/capabilities.d.ts +0 -2
- package/dist/capabilities.js +0 -27
- package/dist/cli.d.ts +0 -6
- package/dist/cli.js +0 -13
- package/dist/command-runner.d.ts +0 -83
- package/dist/command-runner.js +0 -427
- package/dist/editors.d.ts +0 -26
- package/dist/editors.js +0 -144
- package/dist/gh.d.ts +0 -61
- package/dist/gh.js +0 -185
- package/dist/git.d.ts +0 -57
- package/dist/git.js +0 -482
- package/dist/http.d.ts +0 -7
- package/dist/http.js +0 -98
- package/dist/index-node.d.ts +0 -5
- package/dist/index-node.js +0 -51
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -1011
- package/dist/list-directories.d.ts +0 -8
- package/dist/list-directories.js +0 -32
- package/dist/log.d.ts +0 -2
- package/dist/log.js +0 -2
- package/dist/open-browser.d.ts +0 -5
- package/dist/open-browser.js +0 -23
- package/dist/project-commands.d.ts +0 -17
- package/dist/project-commands.js +0 -152
- package/dist/project-path.d.ts +0 -5
- package/dist/project-path.js +0 -33
- package/dist/runtime.d.ts +0 -65
- package/dist/runtime.js +0 -235
- package/dist/static.d.ts +0 -10
- package/dist/static.js +0 -127
- package/dist/types.d.ts +0 -17
- package/dist/types.js +0 -1
- package/dist/utils.d.ts +0 -3
- package/dist/utils.js +0 -26
- package/dist/ws-hub.d.ts +0 -20
- package/dist/ws-hub.js +0 -123
- package/web/dist/assets/index-CRDz04kv.css +0 -1
package/dist/git.js
DELETED
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
const execFileAsync = promisify(execFile);
|
|
4
|
-
const MAX_BUFFER = 1024 * 1024 * 20;
|
|
5
|
-
const execGit = (cwd, args) => execFileAsync("git", args, { cwd, maxBuffer: MAX_BUFFER });
|
|
6
|
-
const execGitAllowNonZero = async (cwd, args) => {
|
|
7
|
-
try {
|
|
8
|
-
return await execGit(cwd, args);
|
|
9
|
-
}
|
|
10
|
-
catch (error) {
|
|
11
|
-
if (error && typeof error === "object" && "stdout" in error) {
|
|
12
|
-
const record = error;
|
|
13
|
-
const toText = (value) => typeof value === "string"
|
|
14
|
-
? value
|
|
15
|
-
: value instanceof Buffer
|
|
16
|
-
? value.toString("utf8")
|
|
17
|
-
: "";
|
|
18
|
-
return {
|
|
19
|
-
stdout: toText(record.stdout),
|
|
20
|
-
stderr: toText(record.stderr),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
export async function gitDiff(cwd, args = [], paths = []) {
|
|
27
|
-
const diffArgs = ["diff", "--no-color", ...args];
|
|
28
|
-
if (paths.length) {
|
|
29
|
-
diffArgs.push("--", ...paths);
|
|
30
|
-
}
|
|
31
|
-
const { stdout } = await execGit(cwd, diffArgs);
|
|
32
|
-
const includeUntracked = !args.some((arg) => ["--cached", "--staged", "--index"].includes(arg));
|
|
33
|
-
if (!includeUntracked) {
|
|
34
|
-
return stdout;
|
|
35
|
-
}
|
|
36
|
-
const { stdout: untrackedRaw } = await execGit(cwd, [
|
|
37
|
-
"ls-files",
|
|
38
|
-
"--others",
|
|
39
|
-
"--exclude-standard",
|
|
40
|
-
"-z",
|
|
41
|
-
]);
|
|
42
|
-
const untracked = untrackedRaw
|
|
43
|
-
.split("\0")
|
|
44
|
-
.map((entry) => entry.trim())
|
|
45
|
-
.filter(Boolean);
|
|
46
|
-
const filteredUntracked = paths.length
|
|
47
|
-
? untracked.filter((file) => paths.some((path) => file === path || file.startsWith(`${path}/`)))
|
|
48
|
-
: untracked;
|
|
49
|
-
if (!filteredUntracked.length) {
|
|
50
|
-
return stdout;
|
|
51
|
-
}
|
|
52
|
-
const untrackedDiffs = [];
|
|
53
|
-
for (const file of filteredUntracked) {
|
|
54
|
-
const { stdout: fileDiff } = await execGitAllowNonZero(cwd, [
|
|
55
|
-
"diff",
|
|
56
|
-
"--no-color",
|
|
57
|
-
"--no-index",
|
|
58
|
-
"--",
|
|
59
|
-
"/dev/null",
|
|
60
|
-
file,
|
|
61
|
-
]);
|
|
62
|
-
if (fileDiff) {
|
|
63
|
-
untrackedDiffs.push(fileDiff.trimEnd());
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (!untrackedDiffs.length) {
|
|
67
|
-
return stdout;
|
|
68
|
-
}
|
|
69
|
-
const base = stdout.trimEnd();
|
|
70
|
-
const combined = [base, ...untrackedDiffs].filter(Boolean).join("\n\n");
|
|
71
|
-
return combined ? `${combined}\n` : combined;
|
|
72
|
-
}
|
|
73
|
-
export async function gitListStagedFiles(cwd) {
|
|
74
|
-
const { stdout } = await execGit(cwd, [
|
|
75
|
-
"diff",
|
|
76
|
-
"--name-only",
|
|
77
|
-
"--cached",
|
|
78
|
-
"-z",
|
|
79
|
-
]);
|
|
80
|
-
return stdout
|
|
81
|
-
.split("\0")
|
|
82
|
-
.map((entry) => entry.trim())
|
|
83
|
-
.filter(Boolean);
|
|
84
|
-
}
|
|
85
|
-
export async function gitStagePaths(cwd, paths) {
|
|
86
|
-
if (!paths.length) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
await execGit(cwd, ["add", "-A", "--", ...paths]);
|
|
90
|
-
}
|
|
91
|
-
const isMissingHeadError = (error) => {
|
|
92
|
-
if (!error || typeof error !== "object" || !("stderr" in error)) {
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
const record = error;
|
|
96
|
-
const message = typeof record.stderr === "string"
|
|
97
|
-
? record.stderr
|
|
98
|
-
: record.stderr instanceof Buffer
|
|
99
|
-
? record.stderr.toString("utf8")
|
|
100
|
-
: "";
|
|
101
|
-
return (message.includes("ambiguous argument 'HEAD'") ||
|
|
102
|
-
message.includes("bad revision 'HEAD'") ||
|
|
103
|
-
message.includes("unknown revision or path"));
|
|
104
|
-
};
|
|
105
|
-
export async function gitUnstagePaths(cwd, paths) {
|
|
106
|
-
if (!paths.length) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
await execGit(cwd, ["reset", "-q", "HEAD", "--", ...paths]);
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
if (!isMissingHeadError(error)) {
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
await execGit(cwd, ["rm", "-r", "--cached", "--", ...paths]);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
export async function gitShowFile(cwd, ref, filePath) {
|
|
120
|
-
if (!ref) {
|
|
121
|
-
throw new Error("invalid_ref");
|
|
122
|
-
}
|
|
123
|
-
if (!filePath) {
|
|
124
|
-
throw new Error("invalid_path");
|
|
125
|
-
}
|
|
126
|
-
const { stdout } = await execGit(cwd, ["show", `${ref}:${filePath}`]);
|
|
127
|
-
return stdout;
|
|
128
|
-
}
|
|
129
|
-
export async function gitLog(cwd, skip = 0, limit = 50) {
|
|
130
|
-
const safeSkip = Number.isFinite(skip) && skip > 0 ? Math.floor(skip) : 0;
|
|
131
|
-
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 50;
|
|
132
|
-
const format = "%H%x1f%h%x1f%an%x1f%aI%x1f%s%x1f%b%x1e";
|
|
133
|
-
const { stdout } = await execGit(cwd, [
|
|
134
|
-
"log",
|
|
135
|
-
`--skip=${safeSkip}`,
|
|
136
|
-
"-n",
|
|
137
|
-
`${safeLimit + 1}`,
|
|
138
|
-
`--format=${format}`,
|
|
139
|
-
]);
|
|
140
|
-
const records = stdout
|
|
141
|
-
.split("\x1e")
|
|
142
|
-
.map((line) => line.trim())
|
|
143
|
-
.filter(Boolean);
|
|
144
|
-
const parsed = records
|
|
145
|
-
.map((line) => {
|
|
146
|
-
const [sha, shortSha, authorName, authoredAt, subject, description] = line.split("\x1f");
|
|
147
|
-
if (!sha) {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
sha,
|
|
152
|
-
shortSha: shortSha || sha.slice(0, 7),
|
|
153
|
-
authorName: authorName || "",
|
|
154
|
-
authoredAt: authoredAt || "",
|
|
155
|
-
subject: subject || "",
|
|
156
|
-
description: description || "",
|
|
157
|
-
};
|
|
158
|
-
})
|
|
159
|
-
.filter((entry) => Boolean(entry));
|
|
160
|
-
const hasMore = parsed.length > safeLimit;
|
|
161
|
-
const items = parsed.slice(0, safeLimit);
|
|
162
|
-
return {
|
|
163
|
-
items,
|
|
164
|
-
hasMore,
|
|
165
|
-
nextSkip: safeSkip + items.length,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
export async function gitCommitDiff(cwd, sha) {
|
|
169
|
-
if (!sha) {
|
|
170
|
-
throw new Error("invalid_commit");
|
|
171
|
-
}
|
|
172
|
-
const { stdout } = await execGit(cwd, [
|
|
173
|
-
"show",
|
|
174
|
-
"--no-color",
|
|
175
|
-
"--no-ext-diff",
|
|
176
|
-
"--format=",
|
|
177
|
-
sha,
|
|
178
|
-
]);
|
|
179
|
-
return stdout;
|
|
180
|
-
}
|
|
181
|
-
export function gitCommitDiffStream(cwd, sha) {
|
|
182
|
-
if (!sha) {
|
|
183
|
-
throw new Error("invalid_commit");
|
|
184
|
-
}
|
|
185
|
-
const child = spawn("git", ["show", "--no-color", "--no-ext-diff", "--format=", sha], { cwd });
|
|
186
|
-
return new ReadableStream({
|
|
187
|
-
start(controller) {
|
|
188
|
-
const onData = (chunk) => {
|
|
189
|
-
controller.enqueue(chunk);
|
|
190
|
-
};
|
|
191
|
-
const onError = (error) => {
|
|
192
|
-
controller.error(error);
|
|
193
|
-
};
|
|
194
|
-
const onClose = (code) => {
|
|
195
|
-
if (code && code !== 0) {
|
|
196
|
-
controller.error(new Error("git_failed"));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
controller.close();
|
|
200
|
-
};
|
|
201
|
-
child.stdout.on("data", onData);
|
|
202
|
-
child.stdout.on("error", onError);
|
|
203
|
-
child.on("error", onError);
|
|
204
|
-
child.on("close", onClose);
|
|
205
|
-
},
|
|
206
|
-
cancel() {
|
|
207
|
-
child.kill();
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
export async function gitCommitFiles(cwd, sha) {
|
|
212
|
-
if (!sha) {
|
|
213
|
-
throw new Error("invalid_commit");
|
|
214
|
-
}
|
|
215
|
-
const { stdout } = await execGit(cwd, [
|
|
216
|
-
"show",
|
|
217
|
-
"--name-status",
|
|
218
|
-
"--no-ext-diff",
|
|
219
|
-
"--format=",
|
|
220
|
-
sha,
|
|
221
|
-
]);
|
|
222
|
-
return stdout
|
|
223
|
-
.split("\n")
|
|
224
|
-
.map((line) => line.trim())
|
|
225
|
-
.filter(Boolean)
|
|
226
|
-
.map((line) => {
|
|
227
|
-
const [rawStatus = "", ...paths] = line.split("\t");
|
|
228
|
-
const statusCode = rawStatus[0] || "";
|
|
229
|
-
const pickPath = statusCode === "R" || statusCode === "C"
|
|
230
|
-
? paths[1] || paths[0] || ""
|
|
231
|
-
: paths[0] || "";
|
|
232
|
-
const previousPath = statusCode === "R" ? paths[0] || "" : "";
|
|
233
|
-
const status = statusCode === "A"
|
|
234
|
-
? "added"
|
|
235
|
-
: statusCode === "D"
|
|
236
|
-
? "deleted"
|
|
237
|
-
: statusCode === "R"
|
|
238
|
-
? "renamed"
|
|
239
|
-
: statusCode === "C"
|
|
240
|
-
? "added"
|
|
241
|
-
: "modified";
|
|
242
|
-
const entry = {
|
|
243
|
-
path: pickPath,
|
|
244
|
-
status,
|
|
245
|
-
};
|
|
246
|
-
if (previousPath && previousPath !== pickPath) {
|
|
247
|
-
entry.previousPath = previousPath;
|
|
248
|
-
}
|
|
249
|
-
return entry;
|
|
250
|
-
})
|
|
251
|
-
.filter((entry) => entry.path);
|
|
252
|
-
}
|
|
253
|
-
export async function gitCommitDiffLineEstimate(cwd, sha) {
|
|
254
|
-
if (!sha) {
|
|
255
|
-
throw new Error("invalid_commit");
|
|
256
|
-
}
|
|
257
|
-
const { stdout } = await execGit(cwd, [
|
|
258
|
-
"show",
|
|
259
|
-
"--numstat",
|
|
260
|
-
"--no-ext-diff",
|
|
261
|
-
"--format=",
|
|
262
|
-
sha,
|
|
263
|
-
]);
|
|
264
|
-
let total = 0;
|
|
265
|
-
for (const line of stdout.split("\n")) {
|
|
266
|
-
const trimmed = line.trim();
|
|
267
|
-
if (!trimmed) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
const [addRaw, delRaw] = trimmed.split("\t");
|
|
271
|
-
if (addRaw === "-" || delRaw === "-") {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
const additions = Number.parseInt(addRaw || "0", 10);
|
|
275
|
-
const deletions = Number.parseInt(delRaw || "0", 10);
|
|
276
|
-
const safeAdditions = Number.isFinite(additions) ? additions : 0;
|
|
277
|
-
const safeDeletions = Number.isFinite(deletions) ? deletions : 0;
|
|
278
|
-
total += safeAdditions + safeDeletions;
|
|
279
|
-
}
|
|
280
|
-
return total;
|
|
281
|
-
}
|
|
282
|
-
const parseGitFileList = (value) => value.split("\0").filter((entry) => entry);
|
|
283
|
-
const buildFileTree = (files) => {
|
|
284
|
-
const root = {
|
|
285
|
-
name: "",
|
|
286
|
-
path: "",
|
|
287
|
-
type: "folder",
|
|
288
|
-
children: new Map(),
|
|
289
|
-
};
|
|
290
|
-
for (const filePath of files) {
|
|
291
|
-
const normalized = filePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
292
|
-
if (!normalized) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
const parts = normalized.split("/").filter((part) => part);
|
|
296
|
-
let current = root;
|
|
297
|
-
let currentPath = "";
|
|
298
|
-
for (const [index, part] of parts.entries()) {
|
|
299
|
-
const nextPath = currentPath ? `${currentPath}/${part}` : part;
|
|
300
|
-
const isFile = index === parts.length - 1;
|
|
301
|
-
const type = isFile ? "file" : "folder";
|
|
302
|
-
let child = current.children.get(part);
|
|
303
|
-
if (!child) {
|
|
304
|
-
child = {
|
|
305
|
-
name: part,
|
|
306
|
-
path: nextPath,
|
|
307
|
-
type,
|
|
308
|
-
children: new Map(),
|
|
309
|
-
};
|
|
310
|
-
current.children.set(part, child);
|
|
311
|
-
}
|
|
312
|
-
current = child;
|
|
313
|
-
currentPath = nextPath;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const toEntry = (node) => {
|
|
317
|
-
if (node.type === "file") {
|
|
318
|
-
return {
|
|
319
|
-
name: node.name,
|
|
320
|
-
path: node.path,
|
|
321
|
-
type: node.type,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
const children = Array.from(node.children.values()).map(toEntry);
|
|
325
|
-
children.sort((a, b) => {
|
|
326
|
-
if (a.type !== b.type) {
|
|
327
|
-
return a.type === "folder" ? -1 : 1;
|
|
328
|
-
}
|
|
329
|
-
return a.name.localeCompare(b.name);
|
|
330
|
-
});
|
|
331
|
-
return {
|
|
332
|
-
name: node.name,
|
|
333
|
-
path: node.path,
|
|
334
|
-
type: node.type,
|
|
335
|
-
children,
|
|
336
|
-
};
|
|
337
|
-
};
|
|
338
|
-
return Array.from(root.children.values())
|
|
339
|
-
.map(toEntry)
|
|
340
|
-
.sort((a, b) => {
|
|
341
|
-
if (a.type !== b.type) {
|
|
342
|
-
return a.type === "folder" ? -1 : 1;
|
|
343
|
-
}
|
|
344
|
-
return a.name.localeCompare(b.name);
|
|
345
|
-
});
|
|
346
|
-
};
|
|
347
|
-
export async function gitListFileTree(cwd) {
|
|
348
|
-
const [tracked, untracked] = await Promise.all([
|
|
349
|
-
execGit(cwd, ["ls-files", "-z"]),
|
|
350
|
-
execGit(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]),
|
|
351
|
-
]);
|
|
352
|
-
const files = new Set([
|
|
353
|
-
...parseGitFileList(tracked.stdout),
|
|
354
|
-
...parseGitFileList(untracked.stdout),
|
|
355
|
-
]);
|
|
356
|
-
return buildFileTree(Array.from(files));
|
|
357
|
-
}
|
|
358
|
-
const DEFAULT_BRANCH_PAGE_SIZE = 30;
|
|
359
|
-
export async function gitListBranches(cwd, options = {}) {
|
|
360
|
-
const { stdout } = await execGit(cwd, [
|
|
361
|
-
"for-each-ref",
|
|
362
|
-
"--sort=-committerdate",
|
|
363
|
-
"--format=%(refname:short)\t%(committerdate:iso8601)",
|
|
364
|
-
"refs/heads",
|
|
365
|
-
]);
|
|
366
|
-
let branches = stdout
|
|
367
|
-
.split("\n")
|
|
368
|
-
.map((line) => line.trim())
|
|
369
|
-
.filter(Boolean)
|
|
370
|
-
.map((line) => {
|
|
371
|
-
const [name = "", updatedAtRaw] = line.split("\t");
|
|
372
|
-
return {
|
|
373
|
-
name,
|
|
374
|
-
updatedAt: updatedAtRaw || null,
|
|
375
|
-
};
|
|
376
|
-
});
|
|
377
|
-
// Apply filter if provided
|
|
378
|
-
if (options.filter) {
|
|
379
|
-
const filterLower = options.filter.toLowerCase();
|
|
380
|
-
branches = branches.filter((b) => b.name.toLowerCase().includes(filterLower));
|
|
381
|
-
}
|
|
382
|
-
// Apply cursor-based pagination
|
|
383
|
-
if (options.cursor) {
|
|
384
|
-
const cursorIndex = branches.findIndex((b) => b.name === options.cursor);
|
|
385
|
-
if (cursorIndex >= 0) {
|
|
386
|
-
branches = branches.slice(cursorIndex + 1);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
// Apply limit
|
|
390
|
-
const limit = options.limit ?? DEFAULT_BRANCH_PAGE_SIZE;
|
|
391
|
-
return branches.slice(0, limit + 1); // Return one extra to detect hasMore
|
|
392
|
-
}
|
|
393
|
-
export async function gitListBranchesPaginated(cwd, options = {}) {
|
|
394
|
-
const limit = options.limit ?? DEFAULT_BRANCH_PAGE_SIZE;
|
|
395
|
-
const allBranches = await gitListBranches(cwd, options);
|
|
396
|
-
const hasMore = allBranches.length > limit;
|
|
397
|
-
const branches = hasMore ? allBranches.slice(0, limit) : allBranches;
|
|
398
|
-
const nextCursor = hasMore
|
|
399
|
-
? (branches[branches.length - 1]?.name ?? null)
|
|
400
|
-
: null;
|
|
401
|
-
const currentBranch = await gitCurrentBranch(cwd);
|
|
402
|
-
const fullBranchList = await gitListBranches(cwd, {}); // Full list for default branch detection
|
|
403
|
-
const defaultBranch = await gitDefaultBranch(cwd, fullBranchList, currentBranch);
|
|
404
|
-
return {
|
|
405
|
-
current: currentBranch,
|
|
406
|
-
defaultBranch,
|
|
407
|
-
branches,
|
|
408
|
-
hasMore,
|
|
409
|
-
nextCursor,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
export async function gitCurrentBranch(cwd) {
|
|
413
|
-
const { stdout } = await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
414
|
-
return stdout.trim();
|
|
415
|
-
}
|
|
416
|
-
export async function gitDefaultBranch(cwd, branches, currentBranch) {
|
|
417
|
-
try {
|
|
418
|
-
const { stdout } = await execGit(cwd, [
|
|
419
|
-
"symbolic-ref",
|
|
420
|
-
"--short",
|
|
421
|
-
"refs/remotes/origin/HEAD",
|
|
422
|
-
]);
|
|
423
|
-
const raw = stdout.trim();
|
|
424
|
-
const parsed = raw.split("/").pop();
|
|
425
|
-
if (parsed) {
|
|
426
|
-
return parsed;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch { }
|
|
430
|
-
const names = new Set(branches.map((branch) => branch.name));
|
|
431
|
-
if (names.has("main")) {
|
|
432
|
-
return "main";
|
|
433
|
-
}
|
|
434
|
-
if (names.has("master")) {
|
|
435
|
-
return "master";
|
|
436
|
-
}
|
|
437
|
-
if (currentBranch && currentBranch !== "HEAD") {
|
|
438
|
-
return currentBranch;
|
|
439
|
-
}
|
|
440
|
-
return branches[0]?.name || "";
|
|
441
|
-
}
|
|
442
|
-
const assertValidBranchName = async (cwd, name) => {
|
|
443
|
-
if (!name) {
|
|
444
|
-
throw new Error("invalid_branch");
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
await execGit(cwd, ["check-ref-format", "--branch", name]);
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
throw new Error("invalid_branch");
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
const branchExists = async (cwd, name) => {
|
|
454
|
-
try {
|
|
455
|
-
await execGit(cwd, [
|
|
456
|
-
"show-ref",
|
|
457
|
-
"--verify",
|
|
458
|
-
"--quiet",
|
|
459
|
-
`refs/heads/${name}`,
|
|
460
|
-
]);
|
|
461
|
-
return true;
|
|
462
|
-
}
|
|
463
|
-
catch {
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
export async function gitCheckoutBranch(cwd, name) {
|
|
468
|
-
await assertValidBranchName(cwd, name);
|
|
469
|
-
const exists = await branchExists(cwd, name);
|
|
470
|
-
if (!exists) {
|
|
471
|
-
throw new Error("branch_not_found");
|
|
472
|
-
}
|
|
473
|
-
await execGit(cwd, ["checkout", name]);
|
|
474
|
-
}
|
|
475
|
-
export async function gitCreateBranch(cwd, name) {
|
|
476
|
-
await assertValidBranchName(cwd, name);
|
|
477
|
-
const exists = await branchExists(cwd, name);
|
|
478
|
-
if (exists) {
|
|
479
|
-
throw new Error("branch_exists");
|
|
480
|
-
}
|
|
481
|
-
await execGit(cwd, ["checkout", "-b", name]);
|
|
482
|
-
}
|
package/dist/http.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare function withCors(response: Response): Response;
|
|
2
|
-
export declare function jsonResponse(status: number, payload: unknown): Response;
|
|
3
|
-
export declare function textResponse(status: number, message: string): Response;
|
|
4
|
-
export declare function streamResponse(status: number, stream: ReadableStream<Uint8Array>): Response;
|
|
5
|
-
export declare function optionsResponse(): Response;
|
|
6
|
-
export declare function readJson(req: Request): Promise<unknown>;
|
|
7
|
-
export declare function mapErrorResponse(error: unknown): Response;
|
package/dist/http.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { log } from "./log.js";
|
|
2
|
-
const CORS_HEADERS = {
|
|
3
|
-
"Access-Control-Allow-Origin": "*",
|
|
4
|
-
"Access-Control-Allow-Methods": "GET,POST,PATCH,OPTIONS",
|
|
5
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
|
6
|
-
};
|
|
7
|
-
export function withCors(response) {
|
|
8
|
-
const headers = new Headers(response.headers);
|
|
9
|
-
for (const [key, value] of Object.entries(CORS_HEADERS)) {
|
|
10
|
-
headers.set(key, value);
|
|
11
|
-
}
|
|
12
|
-
return new Response(response.body, {
|
|
13
|
-
status: response.status,
|
|
14
|
-
statusText: response.statusText,
|
|
15
|
-
headers,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
export function jsonResponse(status, payload) {
|
|
19
|
-
const response = new Response(JSON.stringify(payload), {
|
|
20
|
-
status,
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
return withCors(response);
|
|
26
|
-
}
|
|
27
|
-
export function textResponse(status, message) {
|
|
28
|
-
const response = new Response(message, {
|
|
29
|
-
status,
|
|
30
|
-
headers: {
|
|
31
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
return withCors(response);
|
|
35
|
-
}
|
|
36
|
-
export function streamResponse(status, stream) {
|
|
37
|
-
const response = new Response(stream, {
|
|
38
|
-
status,
|
|
39
|
-
headers: {
|
|
40
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
return withCors(response);
|
|
44
|
-
}
|
|
45
|
-
export function optionsResponse() {
|
|
46
|
-
return withCors(new Response(null, { status: 204 }));
|
|
47
|
-
}
|
|
48
|
-
export async function readJson(req) {
|
|
49
|
-
const text = await req.text();
|
|
50
|
-
if (!text) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
return JSON.parse(text);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
throw new Error("invalid_json");
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
export function mapErrorResponse(error) {
|
|
61
|
-
if (error instanceof Error && error.message === "invalid_json") {
|
|
62
|
-
return jsonResponse(400, { error: "invalid_json" });
|
|
63
|
-
}
|
|
64
|
-
if (error instanceof Error && error.message === "not_a_directory") {
|
|
65
|
-
return jsonResponse(400, { error: "not_a_directory" });
|
|
66
|
-
}
|
|
67
|
-
if (error instanceof Error && error.message === "project_not_found") {
|
|
68
|
-
return jsonResponse(404, { error: "project_not_found" });
|
|
69
|
-
}
|
|
70
|
-
if (error instanceof Error && error.message === "project_required") {
|
|
71
|
-
return jsonResponse(400, { error: "project_required" });
|
|
72
|
-
}
|
|
73
|
-
if (error instanceof Error && error.message === "project_not_git") {
|
|
74
|
-
return jsonResponse(400, { error: "project_not_git" });
|
|
75
|
-
}
|
|
76
|
-
if (error instanceof Error && error.message === "invalid_branch") {
|
|
77
|
-
return jsonResponse(400, { error: "invalid_branch" });
|
|
78
|
-
}
|
|
79
|
-
if (error instanceof Error && error.message === "branch_exists") {
|
|
80
|
-
return jsonResponse(409, { error: "branch_exists" });
|
|
81
|
-
}
|
|
82
|
-
if (error instanceof Error && error.message === "branch_not_found") {
|
|
83
|
-
return jsonResponse(404, { error: "branch_not_found" });
|
|
84
|
-
}
|
|
85
|
-
if (error instanceof Error && error.message === "invalid_path") {
|
|
86
|
-
return jsonResponse(400, { error: "invalid_path" });
|
|
87
|
-
}
|
|
88
|
-
if (error instanceof Error && error.message === "app_server_spawn_failed") {
|
|
89
|
-
return jsonResponse(500, { error: "app_server_spawn_failed" });
|
|
90
|
-
}
|
|
91
|
-
if (error && typeof error === "object" && "code" in error) {
|
|
92
|
-
if (error.code === "ENOENT") {
|
|
93
|
-
return jsonResponse(400, { error: "invalid_path" });
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
log.error(error, "Unhandled server error");
|
|
97
|
-
return jsonResponse(500, { error: "server_error" });
|
|
98
|
-
}
|
package/dist/index-node.d.ts
DELETED
package/dist/index-node.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node.js entry point for the diffact server.
|
|
3
|
-
* Uses @hono/node-server for HTTP and ws for WebSocket.
|
|
4
|
-
*/
|
|
5
|
-
import { serve } from "@hono/node-server";
|
|
6
|
-
import { WebSocketServer } from "ws";
|
|
7
|
-
import { app, hub, PORT } from "./index.js";
|
|
8
|
-
import { log } from "./log.js";
|
|
9
|
-
import { openBrowser } from "./open-browser.js";
|
|
10
|
-
import { getStaticDir, isStaticEnabled } from "./static.js";
|
|
11
|
-
// CLI flags
|
|
12
|
-
const NO_OPEN = process.argv.includes("--no-open");
|
|
13
|
-
// Start HTTP server
|
|
14
|
-
const server = serve({
|
|
15
|
-
fetch: app.fetch,
|
|
16
|
-
port: PORT,
|
|
17
|
-
});
|
|
18
|
-
// Attach WebSocket server
|
|
19
|
-
const wss = new WebSocketServer({ server });
|
|
20
|
-
wss.on("connection", (ws) => {
|
|
21
|
-
log.info("ws open (node)");
|
|
22
|
-
const handlers = hub.open();
|
|
23
|
-
// Adapter: convert ws events to Hono-compatible events
|
|
24
|
-
const wsContext = {
|
|
25
|
-
send: (data) => ws.send(data),
|
|
26
|
-
close: () => ws.close(),
|
|
27
|
-
binaryType: "arraybuffer",
|
|
28
|
-
readyState: 1,
|
|
29
|
-
url: "",
|
|
30
|
-
protocol: "",
|
|
31
|
-
};
|
|
32
|
-
ws.on("message", (data) => {
|
|
33
|
-
const message = typeof data === "string" ? data : data.toString("utf8");
|
|
34
|
-
handlers.onMessage?.({ data: message }, wsContext);
|
|
35
|
-
});
|
|
36
|
-
ws.on("close", (code, reason) => {
|
|
37
|
-
handlers.onClose?.({ code, reason: reason.toString("utf8") }, wsContext);
|
|
38
|
-
});
|
|
39
|
-
ws.on("error", (error) => {
|
|
40
|
-
log.error({ err: error }, "ws error (node)");
|
|
41
|
-
});
|
|
42
|
-
handlers.onOpen?.({}, wsContext);
|
|
43
|
-
});
|
|
44
|
-
const url = `http://localhost:${PORT}`;
|
|
45
|
-
log.info(`diffact server (node) listening on ${url}`);
|
|
46
|
-
if (isStaticEnabled()) {
|
|
47
|
-
log.info(`serving static files from ${getStaticDir()}`);
|
|
48
|
-
}
|
|
49
|
-
if (!NO_OPEN && isStaticEnabled()) {
|
|
50
|
-
openBrowser(url);
|
|
51
|
-
}
|
package/dist/index.d.ts
DELETED