@nervekit/orchestrator 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/domains/agents/run/agent-run-session.d.ts.map +1 -1
- package/dist/domains/agents/run/agent-run-session.js +25 -2
- package/dist/domains/agents/run/agent-run-session.js.map +1 -1
- package/dist/domains/agents/run/agent-runner.d.ts +12 -1
- package/dist/domains/agents/run/agent-runner.d.ts.map +1 -1
- package/dist/domains/agents/run/agent-runner.js +166 -1
- package/dist/domains/agents/run/agent-runner.js.map +1 -1
- package/dist/domains/agents/run/inline-command-results.d.ts +8 -0
- package/dist/domains/agents/run/inline-command-results.d.ts.map +1 -0
- package/dist/domains/agents/run/inline-command-results.js +109 -0
- package/dist/domains/agents/run/inline-command-results.js.map +1 -0
- package/dist/domains/tasks/task-manager-foreground.d.ts.map +1 -1
- package/dist/domains/tasks/task-manager-foreground.js +4 -1
- package/dist/domains/tasks/task-manager-foreground.js.map +1 -1
- package/dist/domains/tasks/task-manager.d.ts +2 -1
- package/dist/domains/tasks/task-manager.d.ts.map +1 -1
- package/dist/domains/tasks/task-manager.js.map +1 -1
- package/dist/domains/tasks/task-supervisor.d.ts.map +1 -1
- package/dist/domains/tasks/task-supervisor.js +12 -1
- package/dist/domains/tasks/task-supervisor.js.map +1 -1
- package/dist/domains/tools/orchestration-tool-dispatcher.d.ts.map +1 -1
- package/dist/domains/tools/orchestration-tool-dispatcher.js +2 -1
- package/dist/domains/tools/orchestration-tool-dispatcher.js.map +1 -1
- package/dist/domains/tools/tool-service.d.ts +2 -0
- package/dist/domains/tools/tool-service.d.ts.map +1 -1
- package/dist/domains/tools/tool-service.js.map +1 -1
- package/dist/registry.d.ts +11 -11
- package/dist/runtime/runtime-state.d.ts +2 -2
- package/dist/web/assets/{dist-DVpGQCt0.js → dist-BaA_eB0I.js} +1 -1
- package/dist/web/assets/{index-DFKLdj_D.css → index-C45Kmr75.css} +1 -1
- package/dist/web/assets/index-DHfIkEia.js +167 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +4 -4
- package/dist/agent-process.d.ts +0 -32
- package/dist/agent-process.d.ts.map +0 -1
- package/dist/agent-process.js +0 -129
- package/dist/agent-process.js.map +0 -1
- package/dist/agent-runner/agent-runner.d.ts +0 -65
- package/dist/agent-runner/agent-runner.d.ts.map +0 -1
- package/dist/agent-runner/agent-runner.js +0 -802
- package/dist/agent-runner/agent-runner.js.map +0 -1
- package/dist/agent-runner/index.d.ts +0 -6
- package/dist/agent-runner/index.d.ts.map +0 -1
- package/dist/agent-runner/index.js +0 -4
- package/dist/agent-runner/index.js.map +0 -1
- package/dist/agent-runner/message-mirror.d.ts +0 -44
- package/dist/agent-runner/message-mirror.d.ts.map +0 -1
- package/dist/agent-runner/message-mirror.js +0 -154
- package/dist/agent-runner/message-mirror.js.map +0 -1
- package/dist/agent-runner/run-state.d.ts +0 -12
- package/dist/agent-runner/run-state.d.ts.map +0 -1
- package/dist/agent-runner/run-state.js +0 -2
- package/dist/agent-runner/run-state.js.map +0 -1
- package/dist/agent-runner/subagent-runner.d.ts +0 -29
- package/dist/agent-runner/subagent-runner.d.ts.map +0 -1
- package/dist/agent-runner/subagent-runner.js +0 -84
- package/dist/agent-runner/subagent-runner.js.map +0 -1
- package/dist/agent-runner/system-prompt-builder.d.ts +0 -19
- package/dist/agent-runner/system-prompt-builder.d.ts.map +0 -1
- package/dist/agent-runner/system-prompt-builder.js +0 -35
- package/dist/agent-runner/system-prompt-builder.js.map +0 -1
- package/dist/agent-runner/tool-draft-streaming.d.ts +0 -2
- package/dist/agent-runner/tool-draft-streaming.d.ts.map +0 -1
- package/dist/agent-runner/tool-draft-streaming.js +0 -5
- package/dist/agent-runner/tool-draft-streaming.js.map +0 -1
- package/dist/agent-suspension-service.d.ts +0 -44
- package/dist/agent-suspension-service.d.ts.map +0 -1
- package/dist/agent-suspension-service.js +0 -96
- package/dist/agent-suspension-service.js.map +0 -1
- package/dist/agent-tool-adapter.d.ts +0 -20
- package/dist/agent-tool-adapter.d.ts.map +0 -1
- package/dist/agent-tool-adapter.js +0 -215
- package/dist/agent-tool-adapter.js.map +0 -1
- package/dist/agents/agent-authority.d.ts +0 -5
- package/dist/agents/agent-authority.d.ts.map +0 -1
- package/dist/agents/agent-authority.js +0 -28
- package/dist/agents/agent-authority.js.map +0 -1
- package/dist/agents/agent-budget.d.ts +0 -3
- package/dist/agents/agent-budget.d.ts.map +0 -1
- package/dist/agents/agent-budget.js +0 -17
- package/dist/agents/agent-budget.js.map +0 -1
- package/dist/agents/agent-status.d.ts +0 -4
- package/dist/agents/agent-status.d.ts.map +0 -1
- package/dist/agents/agent-status.js +0 -10
- package/dist/agents/agent-status.js.map +0 -1
- package/dist/conversation-operations/compaction-service.d.ts +0 -37
- package/dist/conversation-operations/compaction-service.d.ts.map +0 -1
- package/dist/conversation-operations/compaction-service.js +0 -102
- package/dist/conversation-operations/compaction-service.js.map +0 -1
- package/dist/conversation-operations/export-service.d.ts +0 -22
- package/dist/conversation-operations/export-service.d.ts.map +0 -1
- package/dist/conversation-operations/export-service.js +0 -79
- package/dist/conversation-operations/export-service.js.map +0 -1
- package/dist/conversation-operations/import-service.d.ts +0 -20
- package/dist/conversation-operations/import-service.d.ts.map +0 -1
- package/dist/conversation-operations/import-service.js +0 -97
- package/dist/conversation-operations/import-service.js.map +0 -1
- package/dist/conversation-operations/index.d.ts +0 -8
- package/dist/conversation-operations/index.d.ts.map +0 -1
- package/dist/conversation-operations/index.js +0 -6
- package/dist/conversation-operations/index.js.map +0 -1
- package/dist/conversation-operations/navigation-service.d.ts +0 -18
- package/dist/conversation-operations/navigation-service.d.ts.map +0 -1
- package/dist/conversation-operations/navigation-service.js +0 -102
- package/dist/conversation-operations/navigation-service.js.map +0 -1
- package/dist/conversation-operations/summary.d.ts +0 -11
- package/dist/conversation-operations/summary.d.ts.map +0 -1
- package/dist/conversation-operations/summary.js +0 -29
- package/dist/conversation-operations/summary.js.map +0 -1
- package/dist/conversation-runtime.d.ts +0 -96
- package/dist/conversation-runtime.d.ts.map +0 -1
- package/dist/conversation-runtime.js +0 -363
- package/dist/conversation-runtime.js.map +0 -1
- package/dist/conversation-service.d.ts +0 -17
- package/dist/conversation-service.d.ts.map +0 -1
- package/dist/conversation-service.js +0 -56
- package/dist/conversation-service.js.map +0 -1
- package/dist/domains/processes/index.d.ts +0 -10
- package/dist/domains/processes/index.d.ts.map +0 -1
- package/dist/domains/processes/index.js +0 -7
- package/dist/domains/processes/index.js.map +0 -1
- package/dist/domains/processes/process-launch-config.store.d.ts +0 -21
- package/dist/domains/processes/process-launch-config.store.d.ts.map +0 -1
- package/dist/domains/processes/process-launch-config.store.js +0 -49
- package/dist/domains/processes/process-launch-config.store.js.map +0 -1
- package/dist/domains/processes/process-log.service.d.ts +0 -25
- package/dist/domains/processes/process-log.service.d.ts.map +0 -1
- package/dist/domains/processes/process-log.service.js +0 -155
- package/dist/domains/processes/process-log.service.js.map +0 -1
- package/dist/domains/processes/process-manager.d.ts +0 -107
- package/dist/domains/processes/process-manager.d.ts.map +0 -1
- package/dist/domains/processes/process-manager.js +0 -814
- package/dist/domains/processes/process-manager.js.map +0 -1
- package/dist/domains/processes/process-readiness.service.d.ts +0 -7
- package/dist/domains/processes/process-readiness.service.d.ts.map +0 -1
- package/dist/domains/processes/process-readiness.service.js +0 -26
- package/dist/domains/processes/process-readiness.service.js.map +0 -1
- package/dist/domains/processes/process-status.d.ts +0 -5
- package/dist/domains/processes/process-status.d.ts.map +0 -1
- package/dist/domains/processes/process-status.js +0 -13
- package/dist/domains/processes/process-status.js.map +0 -1
- package/dist/domains/processes/process-supervisor.d.ts +0 -34
- package/dist/domains/processes/process-supervisor.d.ts.map +0 -1
- package/dist/domains/processes/process-supervisor.js +0 -211
- package/dist/domains/processes/process-supervisor.js.map +0 -1
- package/dist/domains/processes/process.repository.d.ts +0 -11
- package/dist/domains/processes/process.repository.d.ts.map +0 -1
- package/dist/domains/processes/process.repository.js +0 -31
- package/dist/domains/processes/process.repository.js.map +0 -1
- package/dist/domains/tasks/task-completion.service.d.ts +0 -31
- package/dist/domains/tasks/task-completion.service.d.ts.map +0 -1
- package/dist/domains/tasks/task-completion.service.js +0 -147
- package/dist/domains/tasks/task-completion.service.js.map +0 -1
- package/dist/domains/tasks/task-legacy-migration.d.ts +0 -15
- package/dist/domains/tasks/task-legacy-migration.d.ts.map +0 -1
- package/dist/domains/tasks/task-legacy-migration.js +0 -191
- package/dist/domains/tasks/task-legacy-migration.js.map +0 -1
- package/dist/events.d.ts +0 -2
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -2
- package/dist/events.js.map +0 -1
- package/dist/git/git-service.d.ts +0 -51
- package/dist/git/git-service.d.ts.map +0 -1
- package/dist/git/git-service.js +0 -716
- package/dist/git/git-service.js.map +0 -1
- package/dist/git/git-status.d.ts +0 -24
- package/dist/git/git-status.d.ts.map +0 -1
- package/dist/git/git-status.js +0 -133
- package/dist/git/git-status.js.map +0 -1
- package/dist/harness-manager.d.ts +0 -21
- package/dist/harness-manager.d.ts.map +0 -1
- package/dist/harness-manager.js +0 -105
- package/dist/harness-manager.js.map +0 -1
- package/dist/index-store.d.ts +0 -2
- package/dist/index-store.d.ts.map +0 -1
- package/dist/index-store.js +0 -2
- package/dist/index-store.js.map +0 -1
- package/dist/plan-paths.d.ts +0 -6
- package/dist/plan-paths.d.ts.map +0 -1
- package/dist/plan-paths.js +0 -30
- package/dist/plan-paths.js.map +0 -1
- package/dist/plan-service.d.ts +0 -61
- package/dist/plan-service.d.ts.map +0 -1
- package/dist/plan-service.js +0 -255
- package/dist/plan-service.js.map +0 -1
- package/dist/policy.d.ts +0 -14
- package/dist/policy.d.ts.map +0 -1
- package/dist/policy.js +0 -247
- package/dist/policy.js.map +0 -1
- package/dist/process-manager.d.ts +0 -72
- package/dist/process-manager.d.ts.map +0 -1
- package/dist/process-manager.js +0 -376
- package/dist/process-manager.js.map +0 -1
- package/dist/registry/agent-lifecycle-service.d.ts +0 -38
- package/dist/registry/agent-lifecycle-service.d.ts.map +0 -1
- package/dist/registry/agent-lifecycle-service.js +0 -199
- package/dist/registry/agent-lifecycle-service.js.map +0 -1
- package/dist/registry/conversation-lifecycle-service.d.ts +0 -35
- package/dist/registry/conversation-lifecycle-service.d.ts.map +0 -1
- package/dist/registry/conversation-lifecycle-service.js +0 -136
- package/dist/registry/conversation-lifecycle-service.js.map +0 -1
- package/dist/registry/pinned-command-service.d.ts +0 -11
- package/dist/registry/pinned-command-service.d.ts.map +0 -1
- package/dist/registry/pinned-command-service.js +0 -40
- package/dist/registry/pinned-command-service.js.map +0 -1
- package/dist/registry/project-lifecycle-service.d.ts +0 -22
- package/dist/registry/project-lifecycle-service.d.ts.map +0 -1
- package/dist/registry/project-lifecycle-service.js +0 -87
- package/dist/registry/project-lifecycle-service.js.map +0 -1
- package/dist/repositories/agent-repository.d.ts +0 -12
- package/dist/repositories/agent-repository.d.ts.map +0 -1
- package/dist/repositories/agent-repository.js +0 -33
- package/dist/repositories/agent-repository.js.map +0 -1
- package/dist/repositories/conversation-repository.d.ts +0 -13
- package/dist/repositories/conversation-repository.d.ts.map +0 -1
- package/dist/repositories/conversation-repository.js +0 -39
- package/dist/repositories/conversation-repository.js.map +0 -1
- package/dist/repositories/entry-repository.d.ts +0 -15
- package/dist/repositories/entry-repository.d.ts.map +0 -1
- package/dist/repositories/entry-repository.js +0 -81
- package/dist/repositories/entry-repository.js.map +0 -1
- package/dist/repositories/index.d.ts +0 -7
- package/dist/repositories/index.d.ts.map +0 -1
- package/dist/repositories/index.js +0 -7
- package/dist/repositories/index.js.map +0 -1
- package/dist/repositories/pinned-command-repository.d.ts +0 -10
- package/dist/repositories/pinned-command-repository.d.ts.map +0 -1
- package/dist/repositories/pinned-command-repository.js +0 -28
- package/dist/repositories/pinned-command-repository.js.map +0 -1
- package/dist/repositories/project-repository.d.ts +0 -12
- package/dist/repositories/project-repository.d.ts.map +0 -1
- package/dist/repositories/project-repository.js +0 -33
- package/dist/repositories/project-repository.js.map +0 -1
- package/dist/repositories/prompt-queue-repository.d.ts +0 -28
- package/dist/repositories/prompt-queue-repository.d.ts.map +0 -1
- package/dist/repositories/prompt-queue-repository.js +0 -88
- package/dist/repositories/prompt-queue-repository.js.map +0 -1
- package/dist/routes/process-routes.d.ts +0 -4
- package/dist/routes/process-routes.d.ts.map +0 -1
- package/dist/routes/process-routes.js +0 -43
- package/dist/routes/process-routes.js.map +0 -1
- package/dist/storage.d.ts +0 -2
- package/dist/storage.d.ts.map +0 -1
- package/dist/storage.js +0 -2
- package/dist/storage.js.map +0 -1
- package/dist/tool-service.d.ts +0 -82
- package/dist/tool-service.d.ts.map +0 -1
- package/dist/tool-service.js +0 -409
- package/dist/tool-service.js.map +0 -1
- package/dist/transcription-service.d.ts +0 -15
- package/dist/transcription-service.d.ts.map +0 -1
- package/dist/transcription-service.js +0 -128
- package/dist/transcription-service.js.map +0 -1
- package/dist/usage/anthropic-client.d.ts +0 -9
- package/dist/usage/anthropic-client.d.ts.map +0 -1
- package/dist/usage/anthropic-client.js +0 -207
- package/dist/usage/anthropic-client.js.map +0 -1
- package/dist/usage/codex-client.d.ts +0 -18
- package/dist/usage/codex-client.d.ts.map +0 -1
- package/dist/usage/codex-client.js +0 -316
- package/dist/usage/codex-client.js.map +0 -1
- package/dist/usage/subscription-usage-service.d.ts +0 -42
- package/dist/usage/subscription-usage-service.d.ts.map +0 -1
- package/dist/usage/subscription-usage-service.js +0 -131
- package/dist/usage/subscription-usage-service.js.map +0 -1
- package/dist/web/assets/index-DP6SGqvJ.js +0 -165
- package/dist/worker-manager.d.ts +0 -51
- package/dist/worker-manager.d.ts.map +0 -1
- package/dist/worker-manager.js +0 -116
- package/dist/worker-manager.js.map +0 -1
package/dist/git/git-service.js
DELETED
|
@@ -1,716 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { readdir } from "node:fs/promises";
|
|
4
|
-
import { basename, join, resolve, sep } from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import { HttpError } from "../http/errors.js";
|
|
7
|
-
import { parsePorcelainV2, parseShortstat } from "./git-status.js";
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
const COMMAND_TIMEOUT_MS = 20_000;
|
|
10
|
-
const MAX_BUFFER = 16 * 1024 * 1024;
|
|
11
|
-
const MAX_DISCOVERY_DEPTH = 2;
|
|
12
|
-
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next"]);
|
|
13
|
-
export class GitCommandError extends Error {
|
|
14
|
-
command;
|
|
15
|
-
code;
|
|
16
|
-
stderr;
|
|
17
|
-
constructor(command, code, stderr) {
|
|
18
|
-
super(stderr.trim() || `${command} failed`);
|
|
19
|
-
this.command = command;
|
|
20
|
-
this.code = code;
|
|
21
|
-
this.stderr = stderr;
|
|
22
|
-
this.name = "GitCommandError";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
export class GitService {
|
|
26
|
-
getProject;
|
|
27
|
-
constructor(getProject) {
|
|
28
|
-
this.getProject = getProject;
|
|
29
|
-
}
|
|
30
|
-
// --- low-level exec ---
|
|
31
|
-
async run(bin, cwd, args) {
|
|
32
|
-
try {
|
|
33
|
-
const { stdout, stderr } = await execFileAsync(bin, args, {
|
|
34
|
-
cwd,
|
|
35
|
-
timeout: COMMAND_TIMEOUT_MS,
|
|
36
|
-
maxBuffer: MAX_BUFFER,
|
|
37
|
-
});
|
|
38
|
-
return { stdout, stderr };
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
const err = error;
|
|
42
|
-
if (err.code === "ENOENT") {
|
|
43
|
-
throw new GitCommandError(bin, null, `${bin} executable not found`);
|
|
44
|
-
}
|
|
45
|
-
const code = typeof err.code === "number" ? err.code : null;
|
|
46
|
-
throw new GitCommandError(`${bin} ${args[0] ?? ""}`, code, err.stderr ?? err.message);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
runGit(cwd, args) {
|
|
50
|
-
return this.run("git", cwd, args);
|
|
51
|
-
}
|
|
52
|
-
runGh(cwd, args) {
|
|
53
|
-
return this.run("gh", cwd, args);
|
|
54
|
-
}
|
|
55
|
-
/** Resolve and contain a repo dir relative to the project dir. */
|
|
56
|
-
resolveRepoDir(projectId, relativePath) {
|
|
57
|
-
const project = this.getProject(projectId);
|
|
58
|
-
const root = resolve(project.dir);
|
|
59
|
-
const target = resolve(root, relativePath === "." ? "" : relativePath);
|
|
60
|
-
if (target !== root && !target.startsWith(`${root}${sep}`)) {
|
|
61
|
-
throw new HttpError(400, "GIT_REPO_OUT_OF_SCOPE", "Repository path is outside the project directory.");
|
|
62
|
-
}
|
|
63
|
-
if (!existsSync(join(target, ".git"))) {
|
|
64
|
-
// .git may be a file (worktrees/submodules) — fall back to git check.
|
|
65
|
-
}
|
|
66
|
-
return target;
|
|
67
|
-
}
|
|
68
|
-
async isRepo(dir) {
|
|
69
|
-
try {
|
|
70
|
-
const { stdout } = await this.runGit(dir, [
|
|
71
|
-
"rev-parse",
|
|
72
|
-
"--is-inside-work-tree",
|
|
73
|
-
]);
|
|
74
|
-
return stdout.trim() === "true";
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// --- discovery ---
|
|
81
|
-
async discoverRepos(projectId) {
|
|
82
|
-
const project = this.getProject(projectId);
|
|
83
|
-
const root = resolve(project.dir);
|
|
84
|
-
if (await this.isRepo(root)) {
|
|
85
|
-
return {
|
|
86
|
-
projectIsRepo: true,
|
|
87
|
-
repos: [await this.summarizeRepo(root, ".", project.name)],
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
const repoDirs = await this.walkForRepos(root, root, 0);
|
|
91
|
-
const repos = [];
|
|
92
|
-
for (const dir of repoDirs) {
|
|
93
|
-
const relativePath = dir.slice(root.length + 1) || ".";
|
|
94
|
-
try {
|
|
95
|
-
repos.push(await this.summarizeRepo(dir, relativePath, basename(dir)));
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// Skip repos we cannot summarize (corrupt/unreadable).
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
repos.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
102
|
-
return { projectIsRepo: false, repos };
|
|
103
|
-
}
|
|
104
|
-
async walkForRepos(root, dir, depth) {
|
|
105
|
-
if (depth > MAX_DISCOVERY_DEPTH)
|
|
106
|
-
return [];
|
|
107
|
-
let entries;
|
|
108
|
-
try {
|
|
109
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
const found = [];
|
|
115
|
-
for (const entry of entries) {
|
|
116
|
-
if (!entry.isDirectory())
|
|
117
|
-
continue;
|
|
118
|
-
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name))
|
|
119
|
-
continue;
|
|
120
|
-
const childDir = join(dir, entry.name);
|
|
121
|
-
if (existsSync(join(childDir, ".git"))) {
|
|
122
|
-
// Identified as a repo; do not descend further into it.
|
|
123
|
-
found.push(childDir);
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
found.push(...(await this.walkForRepos(root, childDir, depth + 1)));
|
|
127
|
-
}
|
|
128
|
-
return found;
|
|
129
|
-
}
|
|
130
|
-
async summarizeRepo(repoDir, relativePath, name) {
|
|
131
|
-
const { stdout } = await this.runGit(repoDir, [
|
|
132
|
-
"status",
|
|
133
|
-
"--porcelain=v2",
|
|
134
|
-
"--branch",
|
|
135
|
-
]);
|
|
136
|
-
const { branch, files } = parsePorcelainV2(stdout);
|
|
137
|
-
const baseBranch = await this.detectBaseBranch(repoDir);
|
|
138
|
-
let hasRemote = false;
|
|
139
|
-
try {
|
|
140
|
-
hasRemote =
|
|
141
|
-
(await this.runGit(repoDir, ["remote"])).stdout.trim().length > 0;
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
hasRemote = false;
|
|
145
|
-
}
|
|
146
|
-
const onBaseBranch = branch.head === baseBranch;
|
|
147
|
-
return {
|
|
148
|
-
relativePath,
|
|
149
|
-
absDir: repoDir,
|
|
150
|
-
name,
|
|
151
|
-
isRepo: true,
|
|
152
|
-
currentBranch: branch.head,
|
|
153
|
-
detached: branch.detached,
|
|
154
|
-
ahead: branch.upstream ? (branch.ahead ?? 0) : null,
|
|
155
|
-
behind: branch.upstream ? (branch.behind ?? 0) : null,
|
|
156
|
-
hasUpstream: branch.upstream !== null,
|
|
157
|
-
hasRemote,
|
|
158
|
-
baseBranch,
|
|
159
|
-
onBaseBranch,
|
|
160
|
-
mergedToBase: await this.mergedToBase(repoDir, baseBranch, {
|
|
161
|
-
currentBranch: branch.head,
|
|
162
|
-
detached: branch.detached,
|
|
163
|
-
onBaseBranch,
|
|
164
|
-
}),
|
|
165
|
-
dirty: files.length > 0,
|
|
166
|
-
changeCount: files.length,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
repoName(projectId, relativePath) {
|
|
170
|
-
if (relativePath === ".")
|
|
171
|
-
return this.getProject(projectId).name;
|
|
172
|
-
return basename(relativePath);
|
|
173
|
-
}
|
|
174
|
-
// --- overview ---
|
|
175
|
-
async overview(projectId, relativePath) {
|
|
176
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
177
|
-
const repo = await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath));
|
|
178
|
-
const { stdout: statusOut } = await this.runGit(repoDir, [
|
|
179
|
-
"status",
|
|
180
|
-
"--porcelain=v2",
|
|
181
|
-
]);
|
|
182
|
-
const { files } = parsePorcelainV2(statusOut);
|
|
183
|
-
const stagedCount = files.filter((f) => f.staged).length;
|
|
184
|
-
const untrackedCount = files.filter((f) => f.untracked).length;
|
|
185
|
-
const unstagedCount = files.filter((f) => !f.untracked && f.worktree !== " ").length;
|
|
186
|
-
const unstaged = parseShortstat((await this.runGit(repoDir, ["diff", "--shortstat"])).stdout);
|
|
187
|
-
const staged = parseShortstat((await this.runGit(repoDir, ["diff", "--staged", "--shortstat"])).stdout);
|
|
188
|
-
return {
|
|
189
|
-
repo,
|
|
190
|
-
baseBranch: repo.baseBranch,
|
|
191
|
-
onBaseBranch: repo.onBaseBranch,
|
|
192
|
-
files,
|
|
193
|
-
stagedCount,
|
|
194
|
-
unstagedCount,
|
|
195
|
-
untrackedCount,
|
|
196
|
-
insertions: unstaged.insertions + staged.insertions,
|
|
197
|
-
deletions: unstaged.deletions + staged.deletions,
|
|
198
|
-
recentCommits: await this.recentCommits(repoDir),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
async recentCommits(repoDir) {
|
|
202
|
-
try {
|
|
203
|
-
const { stdout } = await this.runGit(repoDir, [
|
|
204
|
-
"log",
|
|
205
|
-
"-n",
|
|
206
|
-
"10",
|
|
207
|
-
"--pretty=%h%x00%s%x00%cr",
|
|
208
|
-
]);
|
|
209
|
-
return stdout
|
|
210
|
-
.split("\n")
|
|
211
|
-
.filter((line) => line.length > 0)
|
|
212
|
-
.map((line) => {
|
|
213
|
-
const [hash, subject, relativeDate] = line.split("\u0000");
|
|
214
|
-
return {
|
|
215
|
-
hash: hash ?? "",
|
|
216
|
-
subject: subject ?? "",
|
|
217
|
-
relativeDate: relativeDate ?? "",
|
|
218
|
-
};
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
catch {
|
|
222
|
-
return [];
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
async listBranches(projectId, relativePath) {
|
|
226
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
227
|
-
const { stdout } = await this.runGit(repoDir, [
|
|
228
|
-
"for-each-ref",
|
|
229
|
-
"--format=%(refname)%00%(refname:short)%00%(upstream:short)%00%(HEAD)",
|
|
230
|
-
"refs/heads",
|
|
231
|
-
"refs/remotes",
|
|
232
|
-
]);
|
|
233
|
-
const branches = stdout
|
|
234
|
-
.split("\n")
|
|
235
|
-
.filter((line) => line.length > 0)
|
|
236
|
-
.map((line) => {
|
|
237
|
-
const [refname, shortName, upstream, head] = line.split("\u0000");
|
|
238
|
-
if (!refname || !shortName)
|
|
239
|
-
return null;
|
|
240
|
-
if (refname.startsWith("refs/remotes/") &&
|
|
241
|
-
shortName.endsWith("/HEAD")) {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
return {
|
|
245
|
-
name: shortName,
|
|
246
|
-
current: head === "*",
|
|
247
|
-
remote: refname.startsWith("refs/remotes/"),
|
|
248
|
-
upstream: upstream && upstream.length > 0 ? upstream : null,
|
|
249
|
-
};
|
|
250
|
-
})
|
|
251
|
-
.filter((branch) => branch !== null)
|
|
252
|
-
.sort((a, b) => {
|
|
253
|
-
if (a.current !== b.current)
|
|
254
|
-
return a.current ? -1 : 1;
|
|
255
|
-
if (a.remote !== b.remote)
|
|
256
|
-
return a.remote ? 1 : -1;
|
|
257
|
-
return a.name.localeCompare(b.name);
|
|
258
|
-
});
|
|
259
|
-
return { branches };
|
|
260
|
-
}
|
|
261
|
-
async detectBaseBranch(repoDir) {
|
|
262
|
-
try {
|
|
263
|
-
const { stdout } = await this.runGit(repoDir, [
|
|
264
|
-
"symbolic-ref",
|
|
265
|
-
"--quiet",
|
|
266
|
-
"refs/remotes/origin/HEAD",
|
|
267
|
-
]);
|
|
268
|
-
const ref = stdout.trim();
|
|
269
|
-
if (ref.startsWith("refs/remotes/origin/")) {
|
|
270
|
-
return ref.slice("refs/remotes/origin/".length);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
// fall through to probing
|
|
275
|
-
}
|
|
276
|
-
for (const candidate of ["main", "master", "develop"]) {
|
|
277
|
-
if (await this.branchExists(repoDir, candidate))
|
|
278
|
-
return candidate;
|
|
279
|
-
}
|
|
280
|
-
try {
|
|
281
|
-
const { stdout } = await this.runGit(repoDir, [
|
|
282
|
-
"rev-parse",
|
|
283
|
-
"--abbrev-ref",
|
|
284
|
-
"HEAD",
|
|
285
|
-
]);
|
|
286
|
-
return stdout.trim() || "main";
|
|
287
|
-
}
|
|
288
|
-
catch {
|
|
289
|
-
return "main";
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
async branchExists(repoDir, name) {
|
|
293
|
-
try {
|
|
294
|
-
await this.runGit(repoDir, [
|
|
295
|
-
"rev-parse",
|
|
296
|
-
"--verify",
|
|
297
|
-
"--quiet",
|
|
298
|
-
`refs/heads/${name}`,
|
|
299
|
-
]);
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
try {
|
|
304
|
-
await this.runGit(repoDir, [
|
|
305
|
-
"rev-parse",
|
|
306
|
-
"--verify",
|
|
307
|
-
"--quiet",
|
|
308
|
-
`refs/remotes/origin/${name}`,
|
|
309
|
-
]);
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
async comparisonBaseRef(repoDir, baseBranch) {
|
|
318
|
-
for (const candidate of [
|
|
319
|
-
`refs/remotes/origin/${baseBranch}`,
|
|
320
|
-
`refs/heads/${baseBranch}`,
|
|
321
|
-
baseBranch,
|
|
322
|
-
]) {
|
|
323
|
-
try {
|
|
324
|
-
await this.runGit(repoDir, [
|
|
325
|
-
"rev-parse",
|
|
326
|
-
"--verify",
|
|
327
|
-
"--quiet",
|
|
328
|
-
`${candidate}^{commit}`,
|
|
329
|
-
]);
|
|
330
|
-
return candidate;
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
// Try the next possible ref.
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return baseBranch;
|
|
337
|
-
}
|
|
338
|
-
async mergedToBase(repoDir, baseBranch, state) {
|
|
339
|
-
if (state.detached || state.onBaseBranch || !state.currentBranch) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
const baseRef = await this.comparisonBaseRef(repoDir, baseBranch);
|
|
343
|
-
try {
|
|
344
|
-
await this.runGit(repoDir, [
|
|
345
|
-
"merge-base",
|
|
346
|
-
"--is-ancestor",
|
|
347
|
-
"HEAD",
|
|
348
|
-
baseRef,
|
|
349
|
-
]);
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
// --- workflow mutations ---
|
|
357
|
-
async createBranch(projectId, relativePath, name) {
|
|
358
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
359
|
-
try {
|
|
360
|
-
await this.runGit(repoDir, ["check-ref-format", "--branch", name]);
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
throw new HttpError(400, "GIT_INVALID_BRANCH_NAME", `'${name}' is not a valid git branch name.`);
|
|
364
|
-
}
|
|
365
|
-
await this.mapGit(() => this.runGit(repoDir, ["switch", "-c", name]));
|
|
366
|
-
return {
|
|
367
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
async switchBranch(projectId, relativePath, name) {
|
|
371
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
372
|
-
const branches = await this.listBranches(projectId, relativePath);
|
|
373
|
-
const target = branches.branches.find((branch) => branch.name === name);
|
|
374
|
-
if (!target) {
|
|
375
|
-
throw new HttpError(404, "GIT_BRANCH_NOT_FOUND", `Branch '${name}' was not found.`);
|
|
376
|
-
}
|
|
377
|
-
const args = target.remote ? ["switch", "--track", name] : ["switch", name];
|
|
378
|
-
await this.mapGit(() => this.runGit(repoDir, args));
|
|
379
|
-
return {
|
|
380
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
async stageFile(projectId, relativePath, path) {
|
|
384
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
385
|
-
await this.mapGit(() => this.runGit(repoDir, ["add", "--", path]));
|
|
386
|
-
return {
|
|
387
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
async unstageFile(projectId, relativePath, path) {
|
|
391
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
392
|
-
await this.mapGit(() => this.runGit(repoDir, ["restore", "--staged", "--", path]));
|
|
393
|
-
return {
|
|
394
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
async discardFile(projectId, relativePath, path) {
|
|
398
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
399
|
-
const before = parsePorcelainV2((await this.runGit(repoDir, ["status", "--porcelain=v2"])).stdout).files.find((file) => file.path === path || file.renamedFrom === path);
|
|
400
|
-
try {
|
|
401
|
-
await this.runGit(repoDir, ["restore", "--staged", "--", path]);
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
// The path may not be staged; continue with worktree cleanup.
|
|
405
|
-
}
|
|
406
|
-
if (!before?.untracked) {
|
|
407
|
-
try {
|
|
408
|
-
await this.runGit(repoDir, ["restore", "--worktree", "--", path]);
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
// Newly-added or deleted paths may require git clean instead.
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
await this.mapGit(() => this.runGit(repoDir, ["clean", "-f", "--", path]));
|
|
415
|
-
return {
|
|
416
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
async syncBranch(projectId, relativePath) {
|
|
420
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
421
|
-
const repo = await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath));
|
|
422
|
-
if (repo.detached || !repo.currentBranch) {
|
|
423
|
-
throw new HttpError(409, "GIT_DETACHED_HEAD", "Cannot sync a detached HEAD. Check out a branch first.");
|
|
424
|
-
}
|
|
425
|
-
if (!repo.hasRemote) {
|
|
426
|
-
throw new HttpError(409, "GIT_NO_REMOTE", "This repository does not have a remote configured.");
|
|
427
|
-
}
|
|
428
|
-
if (!repo.hasUpstream) {
|
|
429
|
-
await this.mapGit(() => this.runGit(repoDir, [
|
|
430
|
-
"push",
|
|
431
|
-
"-u",
|
|
432
|
-
"origin",
|
|
433
|
-
repo.currentBranch ?? "",
|
|
434
|
-
]));
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
if ((repo.behind ?? 0) > 0) {
|
|
438
|
-
const { files } = parsePorcelainV2((await this.runGit(repoDir, ["status", "--porcelain=v2"])).stdout);
|
|
439
|
-
if (files.length > 0) {
|
|
440
|
-
throw new HttpError(409, "GIT_DIRTY_WORKTREE", "Working tree has uncommitted changes. Commit or stash them before syncing.");
|
|
441
|
-
}
|
|
442
|
-
await this.mapGit(() => this.runGit(repoDir, ["pull", "--ff-only"]));
|
|
443
|
-
}
|
|
444
|
-
if ((repo.ahead ?? 0) > 0) {
|
|
445
|
-
await this.mapGit(() => this.runGit(repoDir, ["push"]));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
async push(projectId, relativePath) {
|
|
453
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
454
|
-
const { stdout: branchOut } = await this.runGit(repoDir, [
|
|
455
|
-
"rev-parse",
|
|
456
|
-
"--abbrev-ref",
|
|
457
|
-
"HEAD",
|
|
458
|
-
]);
|
|
459
|
-
const branch = branchOut.trim();
|
|
460
|
-
if (!branch || branch === "HEAD") {
|
|
461
|
-
throw new HttpError(409, "GIT_DETACHED_HEAD", "Cannot push from a detached HEAD. Check out a branch first.");
|
|
462
|
-
}
|
|
463
|
-
const args = (await this.hasUpstream(repoDir))
|
|
464
|
-
? ["push"]
|
|
465
|
-
: ["push", "-u", "origin", branch];
|
|
466
|
-
await this.mapGit(() => this.runGit(repoDir, args));
|
|
467
|
-
return {
|
|
468
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
async pull(projectId, relativePath) {
|
|
472
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
473
|
-
if (!(await this.hasUpstream(repoDir))) {
|
|
474
|
-
throw new HttpError(409, "GIT_NO_UPSTREAM", "Current branch has no upstream to pull from.");
|
|
475
|
-
}
|
|
476
|
-
const { files } = parsePorcelainV2((await this.runGit(repoDir, ["status", "--porcelain=v2"])).stdout);
|
|
477
|
-
if (files.length > 0) {
|
|
478
|
-
throw new HttpError(409, "GIT_DIRTY_WORKTREE", "Working tree has uncommitted changes. Commit or stash them before pulling.");
|
|
479
|
-
}
|
|
480
|
-
await this.mapGit(() => this.runGit(repoDir, ["pull", "--ff-only"]));
|
|
481
|
-
return {
|
|
482
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
async fetch(projectId, relativePath) {
|
|
486
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
487
|
-
await this.mapGit(() => this.runGit(repoDir, ["fetch", "--prune"]));
|
|
488
|
-
return {
|
|
489
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
async hasUpstream(repoDir) {
|
|
493
|
-
try {
|
|
494
|
-
await this.runGit(repoDir, [
|
|
495
|
-
"rev-parse",
|
|
496
|
-
"--abbrev-ref",
|
|
497
|
-
"--symbolic-full-name",
|
|
498
|
-
"@{u}",
|
|
499
|
-
]);
|
|
500
|
-
return true;
|
|
501
|
-
}
|
|
502
|
-
catch {
|
|
503
|
-
return false;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
// --- GitHub via gh ---
|
|
507
|
-
async githubStatus(projectId, relativePath) {
|
|
508
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
509
|
-
try {
|
|
510
|
-
await this.runGh(repoDir, ["--version"]);
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
return {
|
|
514
|
-
available: false,
|
|
515
|
-
authenticated: false,
|
|
516
|
-
login: null,
|
|
517
|
-
reason: "GitHub CLI (gh) is not installed.",
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
try {
|
|
521
|
-
const { stdout } = await this.runGh(repoDir, [
|
|
522
|
-
"api",
|
|
523
|
-
"user",
|
|
524
|
-
"--jq",
|
|
525
|
-
".login",
|
|
526
|
-
]);
|
|
527
|
-
const login = stdout.trim();
|
|
528
|
-
return {
|
|
529
|
-
available: true,
|
|
530
|
-
authenticated: login.length > 0,
|
|
531
|
-
login: login.length > 0 ? login : null,
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
catch (error) {
|
|
535
|
-
return {
|
|
536
|
-
available: true,
|
|
537
|
-
authenticated: false,
|
|
538
|
-
login: null,
|
|
539
|
-
reason: error instanceof GitCommandError
|
|
540
|
-
? "Not authenticated. Run `gh auth login`."
|
|
541
|
-
: "GitHub authentication check failed.",
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
async listOpenPrs(projectId, relativePath) {
|
|
546
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
547
|
-
const { stdout } = await this.mapGh(() => this.runGh(repoDir, [
|
|
548
|
-
"pr",
|
|
549
|
-
"list",
|
|
550
|
-
"--state",
|
|
551
|
-
"open",
|
|
552
|
-
"--limit",
|
|
553
|
-
"50",
|
|
554
|
-
"--json",
|
|
555
|
-
"number,title,url,state,isDraft,headRefName,baseRefName,updatedAt",
|
|
556
|
-
]));
|
|
557
|
-
const raw = JSON.parse(stdout || "[]");
|
|
558
|
-
const prs = await Promise.all(raw.map(async (pr) => {
|
|
559
|
-
const checks = await this.prChecks(repoDir, pr.number);
|
|
560
|
-
return {
|
|
561
|
-
number: pr.number,
|
|
562
|
-
title: pr.title,
|
|
563
|
-
url: pr.url,
|
|
564
|
-
state: pr.state,
|
|
565
|
-
isDraft: pr.isDraft,
|
|
566
|
-
headRefName: pr.headRefName,
|
|
567
|
-
baseRefName: pr.baseRefName,
|
|
568
|
-
updatedAt: pr.updatedAt,
|
|
569
|
-
checks,
|
|
570
|
-
};
|
|
571
|
-
}));
|
|
572
|
-
return { prs };
|
|
573
|
-
}
|
|
574
|
-
async prChecks(repoDir, number) {
|
|
575
|
-
try {
|
|
576
|
-
const { stdout } = await this.runGh(repoDir, [
|
|
577
|
-
"pr",
|
|
578
|
-
"checks",
|
|
579
|
-
String(number),
|
|
580
|
-
"--json",
|
|
581
|
-
"name,state,link",
|
|
582
|
-
]);
|
|
583
|
-
const raw = JSON.parse(stdout || "[]");
|
|
584
|
-
return summarizeChecks(raw);
|
|
585
|
-
}
|
|
586
|
-
catch {
|
|
587
|
-
return {
|
|
588
|
-
status: "none",
|
|
589
|
-
total: 0,
|
|
590
|
-
passed: 0,
|
|
591
|
-
failed: 0,
|
|
592
|
-
pending: 0,
|
|
593
|
-
runs: [],
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
async prDetail(projectId, relativePath, number) {
|
|
598
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
599
|
-
const { stdout } = await this.mapGh(() => this.runGh(repoDir, [
|
|
600
|
-
"pr",
|
|
601
|
-
"view",
|
|
602
|
-
String(number),
|
|
603
|
-
"--json",
|
|
604
|
-
"number,title,url,state,isDraft,headRefName,baseRefName,updatedAt,createdAt,body,author,additions,deletions,changedFiles,files,commits,mergeable,reviewDecision",
|
|
605
|
-
]));
|
|
606
|
-
const raw = JSON.parse(stdout || "{}");
|
|
607
|
-
const checks = await this.prChecks(repoDir, number);
|
|
608
|
-
const files = (raw.files ?? []).map((file) => ({
|
|
609
|
-
path: file.path,
|
|
610
|
-
additions: file.additions ?? 0,
|
|
611
|
-
deletions: file.deletions ?? 0,
|
|
612
|
-
}));
|
|
613
|
-
const commits = (raw.commits ?? []).map((commit) => ({
|
|
614
|
-
oid: commit.oid,
|
|
615
|
-
abbrev: commit.oid.slice(0, 7),
|
|
616
|
-
messageHeadline: commit.messageHeadline ?? "",
|
|
617
|
-
authoredDate: commit.authoredDate,
|
|
618
|
-
authorName: commit.authors?.[0]?.name,
|
|
619
|
-
}));
|
|
620
|
-
return {
|
|
621
|
-
number: raw.number,
|
|
622
|
-
title: raw.title,
|
|
623
|
-
url: raw.url,
|
|
624
|
-
state: raw.state,
|
|
625
|
-
isDraft: raw.isDraft,
|
|
626
|
-
headRefName: raw.headRefName,
|
|
627
|
-
baseRefName: raw.baseRefName,
|
|
628
|
-
updatedAt: raw.updatedAt,
|
|
629
|
-
createdAt: raw.createdAt,
|
|
630
|
-
body: raw.body ?? "",
|
|
631
|
-
author: raw.author?.login ?? null,
|
|
632
|
-
additions: raw.additions ?? 0,
|
|
633
|
-
deletions: raw.deletions ?? 0,
|
|
634
|
-
changedFiles: raw.changedFiles ?? files.length,
|
|
635
|
-
mergeable: raw.mergeable ?? null,
|
|
636
|
-
reviewDecision: raw.reviewDecision ?? null,
|
|
637
|
-
files,
|
|
638
|
-
commits,
|
|
639
|
-
checks,
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
async checkoutPr(projectId, relativePath, number) {
|
|
643
|
-
const repoDir = this.resolveRepoDir(projectId, relativePath);
|
|
644
|
-
const { files } = parsePorcelainV2((await this.runGit(repoDir, ["status", "--porcelain=v2"])).stdout);
|
|
645
|
-
if (files.length > 0) {
|
|
646
|
-
throw new HttpError(409, "GIT_DIRTY_WORKTREE", "Working tree has uncommitted changes. Commit or stash them before checking out a PR.");
|
|
647
|
-
}
|
|
648
|
-
await this.mapGh(() => this.runGh(repoDir, ["pr", "checkout", String(number)]));
|
|
649
|
-
return {
|
|
650
|
-
repo: await this.summarizeRepo(repoDir, relativePath, this.repoName(projectId, relativePath)),
|
|
651
|
-
number,
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
// --- error mapping ---
|
|
655
|
-
async mapGit(fn) {
|
|
656
|
-
try {
|
|
657
|
-
return await fn();
|
|
658
|
-
}
|
|
659
|
-
catch (error) {
|
|
660
|
-
if (error instanceof GitCommandError) {
|
|
661
|
-
throw new HttpError(409, "GIT_COMMAND_FAILED", error.message);
|
|
662
|
-
}
|
|
663
|
-
throw error;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
async mapGh(fn) {
|
|
667
|
-
try {
|
|
668
|
-
return await fn();
|
|
669
|
-
}
|
|
670
|
-
catch (error) {
|
|
671
|
-
if (error instanceof GitCommandError) {
|
|
672
|
-
const status = error.code === null ? 503 : 409;
|
|
673
|
-
throw new HttpError(status, "GH_COMMAND_FAILED", error.message);
|
|
674
|
-
}
|
|
675
|
-
throw error;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
export function summarizeChecks(runs) {
|
|
680
|
-
let passed = 0;
|
|
681
|
-
let failed = 0;
|
|
682
|
-
let pending = 0;
|
|
683
|
-
const normalized = runs.map((run) => {
|
|
684
|
-
const state = run.state.toUpperCase();
|
|
685
|
-
if (["SUCCESS", "NEUTRAL", "SKIPPED"].includes(state))
|
|
686
|
-
passed += 1;
|
|
687
|
-
else if ([
|
|
688
|
-
"FAILURE",
|
|
689
|
-
"ERROR",
|
|
690
|
-
"CANCELLED",
|
|
691
|
-
"TIMED_OUT",
|
|
692
|
-
"ACTION_REQUIRED",
|
|
693
|
-
].includes(state))
|
|
694
|
-
failed += 1;
|
|
695
|
-
else
|
|
696
|
-
pending += 1;
|
|
697
|
-
return {
|
|
698
|
-
name: run.name,
|
|
699
|
-
status: state.toLowerCase(),
|
|
700
|
-
conclusion: state.toLowerCase(),
|
|
701
|
-
url: run.link,
|
|
702
|
-
};
|
|
703
|
-
});
|
|
704
|
-
const total = normalized.length;
|
|
705
|
-
let status = "none";
|
|
706
|
-
if (total > 0) {
|
|
707
|
-
if (failed > 0)
|
|
708
|
-
status = "failing";
|
|
709
|
-
else if (pending > 0)
|
|
710
|
-
status = "pending";
|
|
711
|
-
else
|
|
712
|
-
status = "passing";
|
|
713
|
-
}
|
|
714
|
-
return { status, total, passed, failed, pending, runs: normalized };
|
|
715
|
-
}
|
|
716
|
-
//# sourceMappingURL=git-service.js.map
|