@cueloop/daemon 0.1.0-alpha.7 → 0.1.0-alpha.70
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/package.json +33 -22
- package/src/api.test.ts +891 -50
- package/src/api.ts +849 -72
- package/src/capabilities.test.ts +57 -0
- package/src/capabilities.ts +77 -0
- package/src/client.test.ts +24 -0
- package/src/client.ts +459 -69
- package/src/curate.ts +109 -0
- package/src/daemon-version.test.ts +93 -0
- package/src/diff-hot-reload.test.ts +186 -0
- package/src/diff-watcher.ts +102 -0
- package/src/herdr-pane.test.ts +240 -0
- package/src/herdr-pane.ts +167 -0
- package/src/herdr-split.test.ts +106 -0
- package/src/herdr-split.ts +92 -0
- package/src/herdr-tab-store.test.ts +96 -0
- package/src/herdr-tab-store.ts +66 -0
- package/src/index.ts +8 -9
- package/src/interruptible-wait.test.ts +127 -0
- package/src/interruptible-wait.ts +66 -0
- package/src/main.ts +4 -1
- package/src/paths.ts +39 -0
- package/src/project-files.test.ts +124 -0
- package/src/project-files.ts +56 -0
- package/src/protocol.ts +79 -7
- package/src/retention.test.ts +182 -0
- package/src/retention.ts +122 -0
- package/src/review.test.ts +255 -28
- package/src/review.ts +164 -62
- package/src/server.test.ts +381 -39
- package/src/server.ts +322 -84
- package/src/share-blob.test.ts +112 -0
- package/src/share-blob.ts +55 -0
- package/src/single-instance.test.ts +38 -11
- package/src/store.conformance.test.ts +41 -0
- package/src/store.jsonl.test.ts +159 -0
- package/src/store.ts +261 -34
- package/src/testing/store-conformance.ts +194 -0
- package/src/validate.test.ts +174 -28
- package/src/validate.ts +226 -16
- package/src/version.ts +6 -0
- package/src/working-tree.ts +149 -0
package/src/version.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** The daemon's build version, read from the package manifest - the handshake
|
|
2
|
+
* uses it so a client never keeps talking to a daemon from an earlier build. */
|
|
3
|
+
|
|
4
|
+
import manifest from "../package.json" with { type: "json" };
|
|
5
|
+
|
|
6
|
+
export const DAEMON_VERSION: string = manifest.version;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Working-tree diff capture for `cueloop diff`, untracked files included. */
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { DiffFileContents, DiffFileStatus } from "@cueloop/schema";
|
|
5
|
+
|
|
6
|
+
async function git(args: string[], cwd: string): Promise<string | null> {
|
|
7
|
+
const gitProcess = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "ignore" });
|
|
8
|
+
const stdout = await new Response(gitProcess.stdout).text();
|
|
9
|
+
|
|
10
|
+
if ((await gitProcess.exited) !== 0) return null;
|
|
11
|
+
|
|
12
|
+
return stdout.trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Map a `git diff --name-status` letter to a diff status; renames/copies read as modified. */
|
|
16
|
+
function statusFromCode(code: string): DiffFileStatus {
|
|
17
|
+
const letter = code.charAt(0);
|
|
18
|
+
if (letter === "D") return "deleted";
|
|
19
|
+
if (letter === "A") return "added";
|
|
20
|
+
|
|
21
|
+
return "modified";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The working tree's changed files as repo-relative path plus status - a fast listing for the changes
|
|
26
|
+
* navigator that never reads file contents (unlike workingTreeDiff, which captures full curation copies).
|
|
27
|
+
* Uses tab-separated name-status so a trimmed leading column can never shift the parse.
|
|
28
|
+
*/
|
|
29
|
+
export async function workingChangeList(
|
|
30
|
+
cwd = process.cwd(),
|
|
31
|
+
): Promise<{ path: string; status: DiffFileStatus }[]> {
|
|
32
|
+
const changes: { path: string; status: DiffFileStatus }[] = [];
|
|
33
|
+
const tracked = (await git(["diff", "--name-status", "HEAD"], cwd)) ?? "";
|
|
34
|
+
|
|
35
|
+
for (const line of tracked.split("\n")) {
|
|
36
|
+
if (line.trim().length === 0) continue;
|
|
37
|
+
const parts = line.split("\t");
|
|
38
|
+
// a rename is "R100<tab>old<tab>new"; the new path (last field) is what the reviewer opens
|
|
39
|
+
changes.push({ path: parts[parts.length - 1]!, status: statusFromCode(parts[0]!) });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const untracked = (await git(["ls-files", "--others", "--exclude-standard"], cwd)) ?? "";
|
|
43
|
+
|
|
44
|
+
for (const path of untracked.split("\n")) {
|
|
45
|
+
if (path.trim().length > 0) changes.push({ path, status: "added" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return changes;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The blob at HEAD for a path, or "" when the path is not in HEAD (new file). */
|
|
52
|
+
async function headContents(path: string, cwd: string): Promise<string> {
|
|
53
|
+
const gitProcess = Bun.spawn(["git", "show", `HEAD:${path}`], {
|
|
54
|
+
cwd,
|
|
55
|
+
stdout: "pipe",
|
|
56
|
+
stderr: "ignore",
|
|
57
|
+
});
|
|
58
|
+
const stdout = await new Response(gitProcess.stdout).text();
|
|
59
|
+
|
|
60
|
+
return (await gitProcess.exited) === 0 ? stdout : "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The working-tree file, or "" when it is gone (deleted). */
|
|
64
|
+
async function workingContents(path: string, cwd: string): Promise<string> {
|
|
65
|
+
try {
|
|
66
|
+
return await Bun.file(join(cwd, path)).text();
|
|
67
|
+
} catch {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A NUL byte marks content git would treat as binary - not text-curatable. */
|
|
73
|
+
function looksBinary(text: string): boolean {
|
|
74
|
+
for (let index = 0; index < text.length; index++) {
|
|
75
|
+
if (text.charCodeAt(index) === 0) return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Full old/new contents for every tracked change against HEAD, classified by
|
|
83
|
+
* git's own status letter. Renames/copies and binary files are omitted: a
|
|
84
|
+
* path+text pair cannot express a rename header or a binary blob, so curation
|
|
85
|
+
* would emit a patch that drops the old path or corrupts the file - leaving them
|
|
86
|
+
* out keeps those changes in the patch but simply not curatable. The
|
|
87
|
+
* NUL-delimited stream survives paths with spaces or unicode.
|
|
88
|
+
*/
|
|
89
|
+
async function trackedFileContents(cwd: string): Promise<DiffFileContents[]> {
|
|
90
|
+
const nameStatus = (await git(["diff", "HEAD", "--name-status", "-z"], cwd)) ?? "";
|
|
91
|
+
const tokens = nameStatus.split("\0").filter((token) => token.length > 0);
|
|
92
|
+
const files: DiffFileContents[] = [];
|
|
93
|
+
let cursor = 0;
|
|
94
|
+
|
|
95
|
+
while (cursor < tokens.length) {
|
|
96
|
+
const code = tokens[cursor++]![0];
|
|
97
|
+
|
|
98
|
+
if (code === "R" || code === "C") {
|
|
99
|
+
cursor += 2;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const path = tokens[cursor++]!;
|
|
103
|
+
const oldContents = code === "A" ? "" : await headContents(path, cwd);
|
|
104
|
+
const newContents = code === "D" ? "" : await workingContents(path, cwd);
|
|
105
|
+
|
|
106
|
+
if (looksBinary(oldContents) || looksBinary(newContents)) continue;
|
|
107
|
+
const status = code === "A" ? "added" : code === "D" ? "deleted" : "modified";
|
|
108
|
+
|
|
109
|
+
files.push({ path, oldContents, newContents, status });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface WorkingTreeDiff {
|
|
116
|
+
patch: string;
|
|
117
|
+
/** Full file contents per curatable changed file, keeping curation applyable. */
|
|
118
|
+
files: DiffFileContents[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function workingTreeDiff(cwd = process.cwd()): Promise<WorkingTreeDiff> {
|
|
122
|
+
const tracked = (await git(["diff", "HEAD"], cwd)) ?? "";
|
|
123
|
+
const files = await trackedFileContents(cwd);
|
|
124
|
+
// untracked files included by default
|
|
125
|
+
const untrackedList = (await git(["ls-files", "--others", "--exclude-standard"], cwd)) ?? "";
|
|
126
|
+
let untracked = "";
|
|
127
|
+
|
|
128
|
+
for (const file of untrackedList.split("\n").filter(Boolean)) {
|
|
129
|
+
// `git diff --no-index` exits 1 when the files differ - that is success here
|
|
130
|
+
const gitProcess = Bun.spawn(["git", "diff", "--no-index", "--", "/dev/null", file], {
|
|
131
|
+
cwd,
|
|
132
|
+
stdout: "pipe",
|
|
133
|
+
stderr: "ignore",
|
|
134
|
+
});
|
|
135
|
+
const stdout = await new Response(gitProcess.stdout).text();
|
|
136
|
+
const code = await gitProcess.exited;
|
|
137
|
+
|
|
138
|
+
if ((code === 0 || code === 1) && stdout.trim()) {
|
|
139
|
+
untracked += stdout.trim() + "\n";
|
|
140
|
+
const newContents = await workingContents(file, cwd);
|
|
141
|
+
|
|
142
|
+
if (!looksBinary(newContents)) {
|
|
143
|
+
files.push({ path: file, oldContents: "", newContents, status: "added" });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { patch: [tracked, untracked].filter(Boolean).join("\n"), files };
|
|
149
|
+
}
|