@nema-project/nema 0.0.0-stage → 0.1.4

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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunks/chunk-HEIQINX3.js +825 -0
  3. package/dist/chunks/chunk-WT3XUFWU.js +86 -0
  4. package/dist/claude/adapter.d.ts +19 -0
  5. package/dist/claude/adapter.js +258 -0
  6. package/dist/claude/events.d.ts +29 -0
  7. package/dist/claude/events.js +75 -0
  8. package/dist/claude/launch.d.ts +22 -0
  9. package/dist/claude/launch.js +80 -0
  10. package/dist/claude/token.d.ts +9 -0
  11. package/dist/claude/token.js +87 -0
  12. package/dist/codex/adapter.d.ts +17 -0
  13. package/dist/codex/adapter.js +151 -0
  14. package/dist/codex/events.d.ts +16 -0
  15. package/dist/codex/events.js +47 -0
  16. package/dist/codex/isolation.d.ts +33 -0
  17. package/dist/codex/isolation.js +115 -0
  18. package/dist/config.d.ts +29 -0
  19. package/dist/config.js +75 -0
  20. package/dist/configCommands.d.ts +13 -0
  21. package/dist/configCommands.js +69 -0
  22. package/dist/daemon/cleanup.d.ts +5 -0
  23. package/dist/daemon/cleanup.js +49 -0
  24. package/dist/daemon/connection.d.ts +22 -0
  25. package/dist/daemon/connection.js +166 -0
  26. package/dist/daemon/rpc.d.ts +14 -0
  27. package/dist/daemon/rpc.js +20 -0
  28. package/dist/daemon/runner.d.ts +34 -0
  29. package/dist/daemon/runner.js +94 -0
  30. package/dist/daemon/setup.d.ts +39 -0
  31. package/dist/daemon/setup.js +95 -0
  32. package/dist/direct/adapter.d.ts +30 -0
  33. package/dist/direct/adapter.js +190 -0
  34. package/dist/direct/authCommands.d.ts +54 -0
  35. package/dist/direct/authCommands.js +268 -0
  36. package/dist/direct/authStore.d.ts +120 -0
  37. package/dist/direct/authStore.js +351 -0
  38. package/dist/direct/byok.d.ts +27 -0
  39. package/dist/direct/byok.js +67 -0
  40. package/dist/direct/codexLogin.d.ts +34 -0
  41. package/dist/direct/codexLogin.js +93 -0
  42. package/dist/direct/jwt.d.ts +4 -0
  43. package/dist/direct/jwt.js +22 -0
  44. package/dist/direct/lock.d.ts +16 -0
  45. package/dist/direct/lock.js +134 -0
  46. package/dist/direct/loop.d.ts +33 -0
  47. package/dist/direct/loop.js +110 -0
  48. package/dist/direct/siwc.d.ts +63 -0
  49. package/dist/direct/siwc.js +403 -0
  50. package/dist/direct/tools.d.ts +17 -0
  51. package/dist/direct/tools.js +104 -0
  52. package/dist/direct/transport.d.ts +177 -0
  53. package/dist/direct/transport.js +437 -0
  54. package/dist/doctor.d.ts +28 -0
  55. package/dist/doctor.js +178 -0
  56. package/dist/extensions/handlers.d.ts +2 -0
  57. package/dist/extensions/handlers.js +10 -0
  58. package/dist/extensions/store.d.ts +11 -0
  59. package/dist/extensions/store.js +132 -0
  60. package/dist/github/client.d.ts +47 -0
  61. package/dist/github/client.js +383 -0
  62. package/dist/github/gh.d.ts +56 -0
  63. package/dist/github/gh.js +385 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +144 -0
  66. package/dist/localSettings.d.ts +8 -0
  67. package/dist/localSettings.js +26 -0
  68. package/dist/mcp/server.d.ts +5 -0
  69. package/dist/mcp/server.js +83 -0
  70. package/dist/ownedProcess.d.ts +39 -0
  71. package/dist/ownedProcess.js +229 -0
  72. package/dist/platform.d.ts +10 -0
  73. package/dist/platform.js +60 -0
  74. package/dist/report/commands.d.ts +41 -0
  75. package/dist/report/commands.js +159 -0
  76. package/dist/report/desktop.d.ts +3 -0
  77. package/dist/report/desktop.js +123 -0
  78. package/dist/report/git.d.ts +5 -0
  79. package/dist/report/git.js +42 -0
  80. package/dist/report/install.d.ts +22 -0
  81. package/dist/report/install.js +183 -0
  82. package/dist/report/send.d.ts +35 -0
  83. package/dist/report/send.js +208 -0
  84. package/dist/report/token.d.ts +5 -0
  85. package/dist/report/token.js +86 -0
  86. package/dist/repos/git.d.ts +48 -0
  87. package/dist/repos/git.js +370 -0
  88. package/dist/repos/graphify.d.ts +32 -0
  89. package/dist/repos/graphify.js +292 -0
  90. package/dist/repos/handlers.d.ts +20 -0
  91. package/dist/repos/handlers.js +255 -0
  92. package/dist/repos/indexState.d.ts +10 -0
  93. package/dist/repos/indexState.js +45 -0
  94. package/dist/repos/pickDirectory.d.ts +11 -0
  95. package/dist/repos/pickDirectory.js +140 -0
  96. package/dist/repos/reportCheck.d.ts +13 -0
  97. package/dist/repos/reportCheck.js +268 -0
  98. package/dist/repos/watcher.d.ts +64 -0
  99. package/dist/repos/watcher.js +202 -0
  100. package/dist/room/client.d.ts +26 -0
  101. package/dist/room/client.js +76 -0
  102. package/dist/secrets/file.d.ts +12 -0
  103. package/dist/secrets/file.js +89 -0
  104. package/dist/secrets/github.d.ts +10 -0
  105. package/dist/secrets/github.js +29 -0
  106. package/dist/secrets/permissions.d.ts +7 -0
  107. package/dist/secrets/permissions.js +86 -0
  108. package/dist/systemGit.d.ts +14 -0
  109. package/dist/systemGit.js +75 -0
  110. package/dist/wp/commands.d.ts +15 -0
  111. package/dist/wp/commands.js +103 -0
  112. package/package.json +26 -4
  113. package/src/report/SKILL.md +21 -0
  114. package/README.md +0 -3
@@ -0,0 +1,370 @@
1
+ import {
2
+ runHelperFile
3
+ } from "../chunks/chunk-WT3XUFWU.js";
4
+ import {
5
+ isBranchName
6
+ } from "../chunks/chunk-HEIQINX3.js";
7
+
8
+ // repos/git.js
9
+ import { execFile } from "node:child_process";
10
+ import { lstat, mkdir, mkdtemp, readdir, realpath, rm, writeFile } from "node:fs/promises";
11
+ import { tmpdir } from "node:os";
12
+ import path from "node:path";
13
+ import { windowsRuntimeEnv } from "../platform.js";
14
+ var MAX_LINES = 400;
15
+ var MAX_BYTES = 65536;
16
+ var SHA_RE = /^[0-9a-f]{7,40}$/;
17
+ var MAX_BUFFER = 256 * 1024 * 1024;
18
+ function commandEnv() {
19
+ return {
20
+ ...windowsRuntimeEnv(process.env),
21
+ PATH: process.env.PATH ?? "",
22
+ HOME: process.env.HOME ?? "",
23
+ LANG: process.env.LANG ?? "",
24
+ GIT_TERMINAL_PROMPT: "0",
25
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes",
26
+ ...process.env.SSH_AUTH_SOCK ? { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK } : {}
27
+ };
28
+ }
29
+ function stripUserinfo(url) {
30
+ return url.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/?#]*@/i, "$1");
31
+ }
32
+ function execBuf(cmd, args, opts) {
33
+ if (opts?.ownedHelpers) {
34
+ return runHelperFile(cmd, args, { env: commandEnv(), timeout: opts.timeout ?? 3e4, maxBuffer: MAX_BUFFER, encoding: "buffer" }, opts.ownedHelpers).then((result) => {
35
+ if (result.error)
36
+ throw Object.assign(result.error, { stdout: result.stdout, stderr: result.stderr });
37
+ return { stdout: result.stdout, stderr: result.stderr };
38
+ });
39
+ }
40
+ return new Promise((resolve, reject) => {
41
+ execFile(cmd, args, {
42
+ env: commandEnv(),
43
+ shell: false,
44
+ timeout: opts?.timeout ?? 3e4,
45
+ maxBuffer: MAX_BUFFER,
46
+ encoding: "buffer"
47
+ }, (err, stdout, stderr) => {
48
+ const out = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout ?? "");
49
+ const errOut = Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr ?? "");
50
+ if (err) {
51
+ reject(Object.assign(err, { stdout: out, stderr: errOut }));
52
+ return;
53
+ }
54
+ resolve({ stdout: out, stderr: errOut });
55
+ });
56
+ });
57
+ }
58
+ function errorText(e) {
59
+ if (e && typeof e === "object" && "stderr" in e) {
60
+ const stderr = e.stderr;
61
+ const text = Buffer.isBuffer(stderr) ? stderr.toString("utf8") : String(stderr ?? "");
62
+ const trimmed = text.trim();
63
+ if (trimmed)
64
+ return trimmed;
65
+ }
66
+ return e instanceof Error ? e.message.trim() : String(e);
67
+ }
68
+ async function gitRaw(repoPath, args, timeout, ownedHelpers) {
69
+ const { stdout } = await execBuf("git", ["-C", repoPath, ...args], { timeout, ownedHelpers });
70
+ return stdout;
71
+ }
72
+ async function gitText(repoPath, args, timeout, ownedHelpers) {
73
+ const text = (await gitRaw(repoPath, args, timeout, ownedHelpers)).toString("utf8");
74
+ return text.endsWith("\n") ? text.slice(0, -1) : text;
75
+ }
76
+ async function canonical(p) {
77
+ try {
78
+ return await realpath(p);
79
+ } catch {
80
+ return path.resolve(p);
81
+ }
82
+ }
83
+ function isUnder(child, parent) {
84
+ const rel = path.relative(parent, child);
85
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
86
+ }
87
+ async function originHeadBranch(repoPath, ownedHelpers) {
88
+ try {
89
+ const ref = await gitText(repoPath, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], void 0, ownedHelpers);
90
+ if (ref.startsWith("origin/"))
91
+ return ref.slice("origin/".length) || "main";
92
+ return ref || "main";
93
+ } catch {
94
+ return "main";
95
+ }
96
+ }
97
+ function normalizeRepoPath(relPath) {
98
+ if (relPath === "" || path.isAbsolute(relPath))
99
+ throw new Error("\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
100
+ const normalized = path.posix.normalize(relPath);
101
+ if (normalized.split("/").includes(".."))
102
+ throw new Error("\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
103
+ return normalized;
104
+ }
105
+ function splitLines(text) {
106
+ if (text === "")
107
+ return [];
108
+ const parts = text.split("\n");
109
+ if (text.endsWith("\n"))
110
+ parts.pop();
111
+ return parts;
112
+ }
113
+ function cutUtf8(text, maxBytes) {
114
+ let bytes = 0;
115
+ let i = 0;
116
+ for (const ch of text) {
117
+ const n = Buffer.byteLength(ch);
118
+ if (bytes + n > maxBytes)
119
+ break;
120
+ bytes += n;
121
+ i += ch.length;
122
+ }
123
+ return text.slice(0, i);
124
+ }
125
+ function parseLsTree(buf) {
126
+ const rows = [];
127
+ for (const entry of buf.toString("utf8").split("\0")) {
128
+ if (!entry)
129
+ continue;
130
+ const tab = entry.indexOf(" ");
131
+ if (tab < 0)
132
+ continue;
133
+ const [mode, type, sha] = entry.slice(0, tab).split(" ");
134
+ rows.push({ mode: mode ?? "", type: type ?? "", sha: sha ?? "", path: entry.slice(tab + 1) });
135
+ }
136
+ return rows;
137
+ }
138
+ function parseNameStatus(buf) {
139
+ const parts = buf.toString("utf8").split("\0");
140
+ if (parts.at(-1) === "")
141
+ parts.pop();
142
+ const rows = [];
143
+ for (let i = 0; i < parts.length; ) {
144
+ const status = parts[i++];
145
+ if (!status)
146
+ break;
147
+ const kind = status[0];
148
+ if (kind === "R" || kind === "C") {
149
+ const from = parts[i++] ?? "";
150
+ if (kind === "R")
151
+ rows.push({ status: "D", path: from });
152
+ rows.push({ status, path: parts[i++] ?? "" });
153
+ } else {
154
+ rows.push({ status, path: parts[i++] ?? "" });
155
+ }
156
+ }
157
+ return rows;
158
+ }
159
+ var TAR_BLOCK = 512;
160
+ function tarSize(header) {
161
+ const raw = header.subarray(124, 136).toString("utf8").replace(/\0.*$/, "").trim();
162
+ if (!raw)
163
+ return 0;
164
+ const size = Number.parseInt(raw, 8);
165
+ return Number.isFinite(size) ? size : 0;
166
+ }
167
+ function tarSpan(buf, offset) {
168
+ if (offset + TAR_BLOCK > buf.length)
169
+ return null;
170
+ const header = buf.subarray(offset, offset + TAR_BLOCK);
171
+ if (header.every((b) => b === 0))
172
+ return { type: "\0", next: buf.length };
173
+ const size = tarSize(header);
174
+ const data = Math.ceil(size / TAR_BLOCK) * TAR_BLOCK;
175
+ return { type: String.fromCharCode(header[156] ?? 0), next: offset + TAR_BLOCK + data };
176
+ }
177
+ function stripSymlinkEntries(buf) {
178
+ const chunks = [];
179
+ const pending = [];
180
+ let i = 0;
181
+ while (i + TAR_BLOCK <= buf.length) {
182
+ const span = tarSpan(buf, i);
183
+ if (!span)
184
+ break;
185
+ if (span.type === "\0") {
186
+ chunks.push(buf.subarray(i));
187
+ break;
188
+ }
189
+ const entry = buf.subarray(i, span.next);
190
+ if (span.type === "x" || span.type === "L" || span.type === "K") {
191
+ pending.push(entry);
192
+ i = span.next;
193
+ continue;
194
+ }
195
+ if (span.type === "2") {
196
+ pending.length = 0;
197
+ i = span.next;
198
+ continue;
199
+ }
200
+ chunks.push(...pending, entry);
201
+ pending.length = 0;
202
+ i = span.next;
203
+ }
204
+ chunks.push(...pending);
205
+ return Buffer.concat(chunks);
206
+ }
207
+ async function emptyDir(destDir) {
208
+ await mkdir(destDir, { recursive: true });
209
+ const st = await lstat(destDir);
210
+ if (st.isSymbolicLink())
211
+ throw new Error("destDir is a symlink");
212
+ if (!st.isDirectory())
213
+ throw new Error("destDir is not a directory");
214
+ for (const name of await readdir(destDir)) {
215
+ await rm(path.join(destDir, name), { recursive: true, force: true });
216
+ }
217
+ }
218
+ async function inspectRepo(repoPath, nemaHome, ownedHelpers) {
219
+ const real = await canonical(repoPath);
220
+ const home = await canonical(nemaHome);
221
+ if (isUnder(real, home))
222
+ throw new Error("\u4E0D\u80FD\u7ED1\u5B9A ~/.nema \u4E4B\u4E0B\u7684\u76EE\u5F55");
223
+ if (isUnder(home, real))
224
+ throw new Error("\u4E0D\u80FD\u7ED1\u5B9A\u5305\u542B ~/.nema \u7684\u76EE\u5F55");
225
+ let toplevel;
226
+ try {
227
+ toplevel = await gitText(repoPath, ["rev-parse", "--show-toplevel"], void 0, ownedHelpers);
228
+ } catch {
229
+ throw new Error("\u4E0D\u662F git \u4ED3\u5E93\u7684\u6839\u76EE\u5F55");
230
+ }
231
+ const root = await canonical(toplevel);
232
+ if (root !== real)
233
+ throw new Error("\u4E0D\u662F git \u4ED3\u5E93\u7684\u6839\u76EE\u5F55");
234
+ const commonRaw = await gitText(repoPath, ["rev-parse", "--git-common-dir"], void 0, ownedHelpers);
235
+ const commonAbs = path.isAbsolute(commonRaw) ? commonRaw : path.resolve(root, commonRaw);
236
+ const gitCommonDir = await canonical(commonAbs);
237
+ const rawUrl = await gitText(repoPath, ["remote", "get-url", "origin"], void 0, ownedHelpers).catch(() => null);
238
+ const remoteUrl = rawUrl === null ? null : stripUserinfo(rawUrl);
239
+ return {
240
+ root,
241
+ gitCommonDir,
242
+ defaultBranch: await originHeadBranch(repoPath, ownedHelpers),
243
+ hasOrigin: remoteUrl !== null,
244
+ remoteUrl
245
+ };
246
+ }
247
+ async function mainRef(repoPath, mainBranch, ownedHelpers) {
248
+ try {
249
+ await gitText(repoPath, ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${mainBranch}`], void 0, ownedHelpers);
250
+ return `origin/${mainBranch}`;
251
+ } catch {
252
+ return mainBranch;
253
+ }
254
+ }
255
+ async function fetchMain(repoPath, mainBranch, ownedHelpers) {
256
+ if (!isBranchName(mainBranch))
257
+ return { ok: false, error: "\u4E3B\u5206\u652F\u540D\u79F0\u65E0\u6548" };
258
+ try {
259
+ const remotes = await gitText(repoPath, ["remote"], void 0, ownedHelpers);
260
+ if (!remotes.split("\n").filter(Boolean).includes("origin"))
261
+ return { ok: false, error: "\u6CA1\u6709 origin" };
262
+ } catch (e) {
263
+ return { ok: false, error: errorText(e) };
264
+ }
265
+ try {
266
+ await gitText(repoPath, ["fetch", "--no-tags", "origin", "--", `refs/heads/${mainBranch}:refs/remotes/origin/${mainBranch}`], 6e4, ownedHelpers);
267
+ return { ok: true };
268
+ } catch (e) {
269
+ return { ok: false, error: errorText(e) };
270
+ }
271
+ }
272
+ async function revParse(repoPath, ref, ownedHelpers) {
273
+ return gitText(repoPath, ["rev-parse", "--verify", ref], void 0, ownedHelpers);
274
+ }
275
+ async function readFileAtRef(repoPath, ref, relPath, range, ownedHelpers) {
276
+ const rel = normalizeRepoPath(relPath);
277
+ const rows = parseLsTree(await gitRaw(repoPath, ["--literal-pathspecs", "ls-tree", "-z", ref, "--", rel], void 0, ownedHelpers));
278
+ const row = rows.length === 1 && rows[0].path === rel ? rows[0] : void 0;
279
+ if (!row)
280
+ throw new Error(`${rel} \u4E0D\u5B58\u5728\u4E8E ${ref}`);
281
+ if (row.mode === "120000")
282
+ throw new Error(`${rel} \u662F\u7B26\u53F7\u94FE\u63A5\uFF0C\u5DF2\u62D2\u7EDD`);
283
+ if (row.type !== "blob")
284
+ throw new Error(`${rel} \u4E0D\u5B58\u5728\u4E8E ${ref}`);
285
+ const lines = splitLines((await gitRaw(repoPath, ["cat-file", "blob", row.sha], void 0, ownedHelpers)).toString("utf8"));
286
+ const startLine = range?.startLine ?? 1;
287
+ const requestedEnd = range?.endLine ?? lines.length;
288
+ let selected = startLine > lines.length ? [] : lines.slice(startLine - 1, requestedEnd);
289
+ let truncated = false;
290
+ if (selected.length > MAX_LINES) {
291
+ selected = selected.slice(0, MAX_LINES);
292
+ truncated = true;
293
+ }
294
+ let text = selected.join("\n");
295
+ if (Buffer.byteLength(text) > MAX_BYTES) {
296
+ text = cutUtf8(text, MAX_BYTES);
297
+ truncated = true;
298
+ }
299
+ const shown = splitLines(text);
300
+ const endLine = shown.length === 0 ? startLine : startLine + shown.length - 1;
301
+ return { path: rel, startLine, endLine, totalLines: lines.length, text, truncated };
302
+ }
303
+ async function missingAtRef(repoPath, ref, paths, ownedHelpers) {
304
+ const missing = [];
305
+ for (const p of paths) {
306
+ const rel = normalizeRepoPath(p);
307
+ try {
308
+ await gitRaw(repoPath, ["--literal-pathspecs", "cat-file", "-e", `${ref}:${rel}`], void 0, ownedHelpers);
309
+ } catch {
310
+ missing.push(p);
311
+ }
312
+ }
313
+ return missing;
314
+ }
315
+ async function isOnMain(repoPath, sha, ref, ownedHelpers) {
316
+ try {
317
+ await gitText(repoPath, ["merge-base", "--is-ancestor", sha, ref], void 0, ownedHelpers);
318
+ return true;
319
+ } catch {
320
+ return false;
321
+ }
322
+ }
323
+ async function changedFiles(repoPath, from, to, mainBranch, ownedHelpers) {
324
+ if (!isBranchName(mainBranch))
325
+ throw new Error("\u4E3B\u5206\u652F\u540D\u79F0\u65E0\u6548");
326
+ const ref = await mainRef(repoPath, mainBranch, ownedHelpers);
327
+ for (const rev of [from, to]) {
328
+ if (rev === ref)
329
+ continue;
330
+ if (!SHA_RE.test(rev) || !await isOnMain(repoPath, rev, ref, ownedHelpers))
331
+ throw new Error(`\u63D0\u4EA4 ${rev} \u4E0D\u5728\u4E3B\u5206\u652F\u4E0A`);
332
+ }
333
+ return diffNameStatus(repoPath, from, to, ownedHelpers);
334
+ }
335
+ async function diffNameStatus(repoPath, from, to, ownedHelpers) {
336
+ for (const rev of [from, to]) {
337
+ if (rev.startsWith("-"))
338
+ throw new Error(`\u65E0\u6548\u7684\u63D0\u4EA4 ${rev}`);
339
+ }
340
+ return parseNameStatus(await gitRaw(repoPath, ["diff", "--name-status", "-M", "-z", from, to, "--"], void 0, ownedHelpers));
341
+ }
342
+ function tarBin() {
343
+ return process.platform === "win32" ? path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
344
+ }
345
+ async function exportSnapshot(repoPath, ref, destDir, ownedHelpers) {
346
+ const archived = await gitRaw(repoPath, ["archive", "--format=tar", "--", ref], 6e4, ownedHelpers);
347
+ const scratch = await mkdtemp(path.join(tmpdir(), "nema-snapshot-"));
348
+ const archivePath = path.join(scratch, "tree.tar");
349
+ try {
350
+ await writeFile(archivePath, stripSymlinkEntries(archived), { mode: 384 });
351
+ await emptyDir(destDir);
352
+ await execBuf(tarBin(), ["-x", "-f", archivePath, "-C", destDir], { timeout: 6e4, ownedHelpers });
353
+ } finally {
354
+ await rm(scratch, { recursive: true, force: true });
355
+ }
356
+ }
357
+ export {
358
+ changedFiles,
359
+ commandEnv,
360
+ diffNameStatus,
361
+ exportSnapshot,
362
+ fetchMain,
363
+ gitRaw,
364
+ gitText,
365
+ inspectRepo,
366
+ mainRef,
367
+ missingAtRef,
368
+ readFileAtRef,
369
+ revParse
370
+ };
@@ -0,0 +1,32 @@
1
+ import type { OwnedHelperScope } from "@nema/shared/ownedHelper";
2
+ export declare function graphifyEnv(snapshotRoot: string): NodeJS.ProcessEnv;
3
+ /**
4
+ * Where graphify is and how long a call may run. Tests pass a fake binary.
5
+ * An omitted `bin` is the packaged file, never a `PATH` lookup.
6
+ * Read at call time, so a caller may fill the object in after handing it over.
7
+ */
8
+ export type GraphifyOpts = {
9
+ bin?: string;
10
+ timeoutMs?: number;
11
+ ownedHelpers?: OwnedHelperScope;
12
+ };
13
+ /**
14
+ * Full `extract` of a repository. The one-minute cap kills a monorepo mid-build and reports
15
+ * the same "graphify 已中断" as a cancel. Stays under the hub's 5-minute index RPC so fetch
16
+ * and the snapshot export still fit.
17
+ * ponytail: a larger repo needs this and INDEX_RPC_TIMEOUT_MS raised together.
18
+ */
19
+ export declare const EXTRACT_TIMEOUT_MS = 180000;
20
+ /** `tools/graphify/graphify` under the payload, or `NEMA_GRAPHIFY_BIN` when that is set. */
21
+ export declare function packagedGraphifyBin(env?: NodeJS.ProcessEnv): string;
22
+ export declare function buildIndex(snapshotRoot: string, opts?: GraphifyOpts): Promise<void>;
23
+ export declare function queryIndex(snapshotRoot: string, kind: "query" | "explain" | "path", args: string[], opts?: GraphifyOpts): Promise<string>;
24
+ /** Version text when `graphify --version` runs; the install hint when the binary is missing. */
25
+ export declare function graphifyInstallStatus(opts?: GraphifyOpts): Promise<{
26
+ installed: boolean;
27
+ detail: string;
28
+ }>;
29
+ export declare function updateIndex(snapshotRoot: string, changed: {
30
+ status: string;
31
+ path: string;
32
+ }[], repoPath: string, ref: string, opts?: GraphifyOpts): Promise<void>;
@@ -0,0 +1,292 @@
1
+ import {
2
+ runHelperFile
3
+ } from "../chunks/chunk-WT3XUFWU.js";
4
+
5
+ // repos/graphify.js
6
+ import { execFile } from "node:child_process";
7
+ import { lstat, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
8
+ import { tmpdir } from "node:os";
9
+ import path from "node:path";
10
+ import { commandSpec, windowsRuntimeEnv } from "../platform.js";
11
+ var MAX_OUT = 65536;
12
+ var MAX_BUFFER = 256 * 1024 * 1024;
13
+ var INSTALL_HINT = "\u968F\u5305\u7684 Graphify \u8FD0\u884C\u73AF\u5883\u7F3A\u5931";
14
+ function graphifyEnv(snapshotRoot) {
15
+ return {
16
+ ...windowsRuntimeEnv(process.env),
17
+ PATH: process.env.PATH ?? "",
18
+ LANG: process.env.LANG ?? "",
19
+ HOME: path.join(snapshotRoot, "home"),
20
+ GRAPHIFY_BACKEND: "none",
21
+ GRAPHIFY_QUERY_LOG_DISABLE: "1"
22
+ };
23
+ }
24
+ var DEFAULT_TIMEOUT_MS = 6e4;
25
+ var EXTRACT_TIMEOUT_MS = 18e4;
26
+ function packagedGraphifyBin(env = process.env) {
27
+ const name = process.platform === "win32" ? "graphify.exe" : "graphify";
28
+ const fromEnv = env.NEMA_GRAPHIFY_BIN;
29
+ if (typeof fromEnv === "string" && fromEnv !== "")
30
+ return fromEnv;
31
+ const root = typeof env.NEMA_PAYLOAD_ROOT === "string" && env.NEMA_PAYLOAD_ROOT !== "" ? env.NEMA_PAYLOAD_ROOT : path.dirname(process.execPath);
32
+ return path.join(root, "tools", "graphify", name);
33
+ }
34
+ function graphifyBin(opts) {
35
+ return opts?.bin || packagedGraphifyBin();
36
+ }
37
+ function graphifyTimeout(opts) {
38
+ const n = opts?.timeoutMs;
39
+ return n !== void 0 && Number.isFinite(n) && n > 0 ? n : DEFAULT_TIMEOUT_MS;
40
+ }
41
+ function extractCallOpts(opts) {
42
+ if (opts?.timeoutMs !== void 0 && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0)
43
+ return opts;
44
+ return { ...opts, timeoutMs: EXTRACT_TIMEOUT_MS };
45
+ }
46
+ function cutUtf8(text, maxBytes) {
47
+ let bytes = 0;
48
+ let index = 0;
49
+ for (const ch of text) {
50
+ const size = Buffer.byteLength(ch);
51
+ if (bytes + size > maxBytes)
52
+ break;
53
+ bytes += size;
54
+ index += ch.length;
55
+ }
56
+ return text.slice(0, index);
57
+ }
58
+ function exitCode(error) {
59
+ if (error.code === "ENOENT")
60
+ return "missing";
61
+ if (error.signal || error.killed || error.code == null || error.code === 0)
62
+ return "killed";
63
+ if (typeof error.code === "number")
64
+ return error.code;
65
+ const parsed = Number(error.code);
66
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : "killed";
67
+ }
68
+ function stopChild(child) {
69
+ try {
70
+ child.kill("SIGTERM");
71
+ } catch {
72
+ return;
73
+ }
74
+ const sigkill = setTimeout(() => {
75
+ try {
76
+ child.kill("SIGKILL");
77
+ } catch {
78
+ }
79
+ }, 200);
80
+ child.once("exit", () => clearTimeout(sigkill));
81
+ }
82
+ function runGraphify(snapshotRoot, args, cwd, opts) {
83
+ const bin = graphifyBin(opts);
84
+ const command = commandSpec(bin, args, graphifyEnv(snapshotRoot));
85
+ if (opts?.ownedHelpers) {
86
+ return runHelperFile(command.bin, command.args, {
87
+ cwd,
88
+ env: graphifyEnv(snapshotRoot),
89
+ timeout: graphifyTimeout(opts),
90
+ encoding: "buffer",
91
+ maxBuffer: 1024 * 1024
92
+ }, opts.ownedHelpers).then((result) => {
93
+ if (result.error) {
94
+ const code = exitCode(result.error);
95
+ if (code === "missing")
96
+ throw new Error(INSTALL_HINT);
97
+ if (code === "killed")
98
+ throw new Error("graphify \u5DF2\u4E2D\u65AD");
99
+ return { code, stdout: result.stdout, stderr: result.stderr };
100
+ }
101
+ return { code: 0, stdout: result.stdout, stderr: result.stderr };
102
+ });
103
+ }
104
+ return new Promise((resolve, reject) => {
105
+ let settled = false;
106
+ let timedOut = false;
107
+ let timer;
108
+ const child = execFile(command.bin, command.args, { cwd, env: graphifyEnv(snapshotRoot), shell: false, encoding: "buffer", maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
109
+ if (settled)
110
+ return;
111
+ settled = true;
112
+ clearTimeout(timer);
113
+ const out = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout ?? "");
114
+ const errOut = Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr ?? "");
115
+ if (timedOut) {
116
+ reject(new Error("graphify \u5DF2\u4E2D\u65AD"));
117
+ return;
118
+ }
119
+ if (!error) {
120
+ resolve({ code: 0, stdout: out, stderr: errOut });
121
+ return;
122
+ }
123
+ const code = exitCode(error);
124
+ if (code === "missing") {
125
+ reject(new Error(INSTALL_HINT));
126
+ return;
127
+ }
128
+ if (code === "killed") {
129
+ reject(new Error("graphify \u5DF2\u4E2D\u65AD"));
130
+ return;
131
+ }
132
+ resolve({ code, stdout: out, stderr: errOut });
133
+ });
134
+ timer = setTimeout(() => {
135
+ if (settled)
136
+ return;
137
+ timedOut = true;
138
+ stopChild(child);
139
+ }, graphifyTimeout(opts));
140
+ });
141
+ }
142
+ function finish(result) {
143
+ if (result.code === 3)
144
+ throw new Error("\u540D\u79F0\u4E0D\u552F\u4E00");
145
+ if (result.code === 4)
146
+ throw new Error("\u6CA1\u6709\u5339\u914D");
147
+ if (result.code !== 0) {
148
+ const text = result.stderr.toString("utf8").trim();
149
+ throw new Error(text || `graphify exited ${result.code}`);
150
+ }
151
+ return cutUtf8(result.stdout.toString("utf8"), MAX_OUT);
152
+ }
153
+ async function buildIndex(snapshotRoot, opts) {
154
+ const src = path.join(snapshotRoot, "src");
155
+ await mkdir(path.join(snapshotRoot, "home"), { recursive: true });
156
+ finish(await runGraphify(snapshotRoot, ["extract", src, "--code-only"], snapshotRoot, extractCallOpts(opts)));
157
+ }
158
+ function graphJson(snapshotRoot) {
159
+ return path.join(snapshotRoot, "src", "graphify-out", "graph.json");
160
+ }
161
+ async function queryIndex(snapshotRoot, kind, args, opts) {
162
+ for (const arg of args) {
163
+ if (arg.startsWith("-"))
164
+ throw new Error(`\u53C2\u6570\u4E0D\u80FD\u4EE5 - \u5F00\u5934\uFF1A${arg}`);
165
+ }
166
+ return finish(await runGraphify(snapshotRoot, [kind, ...args, "--graph", graphJson(snapshotRoot)], snapshotRoot, opts));
167
+ }
168
+ async function graphifyInstallStatus(opts) {
169
+ const dir = await mkdtemp(path.join(tmpdir(), "nema-graphify-version-"));
170
+ try {
171
+ await mkdir(path.join(dir, "home"), { recursive: true });
172
+ const detail = finish(await runGraphify(dir, ["--version"], dir, opts)).trim();
173
+ return { installed: true, detail };
174
+ } catch (e) {
175
+ const message = e instanceof Error ? e.message : String(e);
176
+ if (message === INSTALL_HINT)
177
+ return { installed: false, detail: message };
178
+ throw e;
179
+ } finally {
180
+ await rm(dir, { recursive: true, force: true });
181
+ }
182
+ }
183
+ function gitEnv() {
184
+ return {
185
+ ...windowsRuntimeEnv(process.env),
186
+ PATH: process.env.PATH ?? "",
187
+ HOME: process.env.HOME ?? "",
188
+ LANG: process.env.LANG ?? "",
189
+ GIT_TERMINAL_PROMPT: "0"
190
+ };
191
+ }
192
+ function git(repoPath, args, ownedHelpers) {
193
+ if (ownedHelpers) {
194
+ return runHelperFile("git", ["-C", repoPath, ...args], {
195
+ env: gitEnv(),
196
+ encoding: "buffer",
197
+ maxBuffer: MAX_BUFFER
198
+ }, ownedHelpers).then((result) => {
199
+ if (result.error) {
200
+ const text = result.stderr.toString("utf8").trim();
201
+ throw new Error(text || result.error.message);
202
+ }
203
+ return result.stdout;
204
+ });
205
+ }
206
+ return new Promise((resolve, reject) => {
207
+ execFile("git", ["-C", repoPath, ...args], { env: gitEnv(), shell: false, encoding: "buffer", maxBuffer: MAX_BUFFER }, (error, stdout, stderr) => {
208
+ const out = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout ?? "");
209
+ if (error) {
210
+ const text = (Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr ?? "")).toString("utf8").trim();
211
+ reject(new Error(text || error.message));
212
+ return;
213
+ }
214
+ resolve(out);
215
+ });
216
+ });
217
+ }
218
+ function normalizeRepoPath(relPath) {
219
+ if (relPath === "" || path.isAbsolute(relPath))
220
+ throw new Error("\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
221
+ const normalized = path.posix.normalize(relPath);
222
+ if (normalized.split("/").includes(".."))
223
+ throw new Error("\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
224
+ return normalized;
225
+ }
226
+ function parseLsTree(buf) {
227
+ const rows = [];
228
+ for (const entry of buf.toString("utf8").split("\0")) {
229
+ if (!entry)
230
+ continue;
231
+ const tab = entry.indexOf(" ");
232
+ if (tab < 0)
233
+ continue;
234
+ const [mode, type, sha] = entry.slice(0, tab).split(" ");
235
+ rows.push({ mode: mode ?? "", type: type ?? "", sha: sha ?? "", path: entry.slice(tab + 1) });
236
+ }
237
+ return rows;
238
+ }
239
+ async function blobAt(repoPath, ref, relPath, ownedHelpers) {
240
+ const rel = normalizeRepoPath(relPath);
241
+ const rows = parseLsTree(await git(repoPath, ["--literal-pathspecs", "ls-tree", "-z", ref, "--", rel], ownedHelpers));
242
+ const row = rows.length === 1 && rows[0]?.path === rel ? rows[0] : void 0;
243
+ if (!row || row.type !== "blob")
244
+ throw new Error(`${rel} \u4E0D\u5B58\u5728\u4E8E ${ref}`);
245
+ if (row.mode === "120000")
246
+ return null;
247
+ return git(repoPath, ["cat-file", "blob", row.sha], ownedHelpers);
248
+ }
249
+ function destination(src, relPath) {
250
+ const rel = normalizeRepoPath(relPath);
251
+ const dest = path.resolve(src, rel);
252
+ const fromSrc = path.relative(src, dest);
253
+ if (fromSrc === "" || fromSrc === ".." || fromSrc.startsWith(`..${path.sep}`) || path.isAbsolute(fromSrc)) {
254
+ throw new Error("\u8DEF\u5F84\u5FC5\u987B\u662F\u4ED3\u5E93\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
255
+ }
256
+ return dest;
257
+ }
258
+ async function writeInside(src, relPath, data) {
259
+ const dest = destination(src, relPath);
260
+ await mkdir(path.dirname(dest), { recursive: true });
261
+ try {
262
+ if ((await lstat(dest)).isSymbolicLink())
263
+ await rm(dest);
264
+ } catch {
265
+ }
266
+ await writeFile(dest, data);
267
+ }
268
+ async function updateIndex(snapshotRoot, changed, repoPath, ref, opts) {
269
+ const src = path.join(snapshotRoot, "src");
270
+ await mkdir(src, { recursive: true });
271
+ for (const file of changed) {
272
+ if (file.status.startsWith("D")) {
273
+ await rm(destination(src, file.path), { force: true });
274
+ continue;
275
+ }
276
+ const blob = await blobAt(repoPath, ref, file.path, opts?.ownedHelpers);
277
+ if (blob === null)
278
+ continue;
279
+ await writeInside(src, file.path, blob);
280
+ }
281
+ await mkdir(path.join(snapshotRoot, "home"), { recursive: true });
282
+ finish(await runGraphify(snapshotRoot, ["update", src, "--code-only"], snapshotRoot, opts));
283
+ }
284
+ export {
285
+ EXTRACT_TIMEOUT_MS,
286
+ buildIndex,
287
+ graphifyEnv,
288
+ graphifyInstallStatus,
289
+ packagedGraphifyBin,
290
+ queryIndex,
291
+ updateIndex
292
+ };