@akiojin/gwt 4.11.6 → 4.12.1
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/bin/gwt.js +36 -10
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +81 -24
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/App.solid.d.ts.map +1 -1
- package/dist/cli/ui/App.solid.js +280 -50
- package/dist/cli/ui/App.solid.js.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
- package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.js +2 -1
- package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.js +67 -13
- package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.d.ts +5 -0
- package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.js +50 -70
- package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
- package/dist/cli/ui/core/theme.d.ts +9 -0
- package/dist/cli/ui/core/theme.d.ts.map +1 -1
- package/dist/cli/ui/core/theme.js +21 -0
- package/dist/cli/ui/core/theme.js.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
- package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
- package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
- package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
- package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -0
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +29 -7
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts +14 -0
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +61 -3
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/cli/ui/utils/installedVersionCache.d.ts +33 -0
- package/dist/cli/ui/utils/installedVersionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/installedVersionCache.js +59 -0
- package/dist/cli/ui/utils/installedVersionCache.js.map +1 -0
- package/dist/cli/ui/utils/modelOptions.d.ts.map +1 -1
- package/dist/cli/ui/utils/modelOptions.js +16 -0
- package/dist/cli/ui/utils/modelOptions.js.map +1 -1
- package/dist/cli/ui/utils/versionCache.d.ts +37 -0
- package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionCache.js +70 -0
- package/dist/cli/ui/utils/versionCache.js.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.js +89 -0
- package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
- package/dist/codex.d.ts +1 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +95 -25
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -1
- package/dist/config/index.js.map +1 -1
- package/dist/gemini.d.ts +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +36 -3
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +43 -8
- package/dist/launcher.js.map +1 -1
- package/dist/logging/agentOutput.d.ts +21 -0
- package/dist/logging/agentOutput.d.ts.map +1 -0
- package/dist/logging/agentOutput.js +164 -0
- package/dist/logging/agentOutput.js.map +1 -0
- package/dist/logging/formatter.d.ts.map +1 -1
- package/dist/logging/formatter.js +18 -4
- package/dist/logging/formatter.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +2 -0
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/reader.d.ts +22 -0
- package/dist/logging/reader.d.ts.map +1 -1
- package/dist/logging/reader.js +116 -1
- package/dist/logging/reader.js.map +1 -1
- package/dist/opentui/index.solid.js +2575 -888
- package/dist/services/codingAgentResolver.d.ts.map +1 -1
- package/dist/services/codingAgentResolver.js +8 -6
- package/dist/services/codingAgentResolver.js.map +1 -1
- package/dist/services/dependency-installer.js +2 -2
- package/dist/services/dependency-installer.js.map +1 -1
- package/dist/shared/codingAgentConstants.d.ts +3 -0
- package/dist/shared/codingAgentConstants.d.ts.map +1 -1
- package/dist/shared/codingAgentConstants.js +66 -0
- package/dist/shared/codingAgentConstants.js.map +1 -1
- package/dist/utils/bun-runtime.d.ts +12 -0
- package/dist/utils/bun-runtime.d.ts.map +1 -0
- package/dist/utils/bun-runtime.js +13 -0
- package/dist/utils/bun-runtime.js.map +1 -0
- package/dist/utils/session/common.d.ts +8 -0
- package/dist/utils/session/common.d.ts.map +1 -1
- package/dist/utils/session/common.js +22 -0
- package/dist/utils/session/common.js.map +1 -1
- package/dist/utils/session/parsers/claude.d.ts +10 -4
- package/dist/utils/session/parsers/claude.d.ts.map +1 -1
- package/dist/utils/session/parsers/claude.js +64 -18
- package/dist/utils/session/parsers/claude.js.map +1 -1
- package/dist/utils/session/parsers/codex.d.ts.map +1 -1
- package/dist/utils/session/parsers/codex.js +47 -28
- package/dist/utils/session/parsers/codex.js.map +1 -1
- package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
- package/dist/utils/session/parsers/gemini.js +43 -6
- package/dist/utils/session/parsers/gemini.js.map +1 -1
- package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
- package/dist/utils/session/parsers/opencode.js +43 -6
- package/dist/utils/session/parsers/opencode.js.map +1 -1
- package/dist/utils/session/types.d.ts +7 -0
- package/dist/utils/session/types.d.ts.map +1 -1
- package/dist/web/client/src/components/ui/alert.d.ts +1 -1
- package/dist/worktree.d.ts +4 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +21 -15
- package/dist/worktree.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +99 -28
- package/src/cli/ui/App.solid.tsx +373 -51
- package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +921 -1
- package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
- package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
- package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
- package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
- package/src/cli/ui/__tests__/solid/components/WizardController.test.tsx +71 -0
- package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +95 -2
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
- package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
- package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
- package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
- package/src/cli/ui/components/solid/WizardController.tsx +85 -12
- package/src/cli/ui/components/solid/WizardSteps.tsx +78 -90
- package/src/cli/ui/core/theme.ts +32 -0
- package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
- package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
- package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
- package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
- package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
- package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
- package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
- package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
- package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
- package/src/cli/ui/types.ts +1 -0
- package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
- package/src/cli/ui/utils/__tests__/installedVersionCache.test.ts +46 -0
- package/src/cli/ui/utils/branchFormatter.ts +35 -7
- package/src/cli/ui/utils/continueSession.ts +90 -3
- package/src/cli/ui/utils/installedVersionCache.ts +84 -0
- package/src/cli/ui/utils/modelOptions.test.ts +6 -0
- package/src/cli/ui/utils/modelOptions.ts +16 -0
- package/src/cli/ui/utils/versionCache.ts +93 -0
- package/src/cli/ui/utils/versionFetcher.ts +120 -0
- package/src/codex.ts +124 -26
- package/src/config/__tests__/saveSession.test.ts +2 -2
- package/src/config/index.ts +11 -1
- package/src/gemini.ts +50 -4
- package/src/index.test.ts +16 -10
- package/src/index.ts +41 -1
- package/src/launcher.ts +49 -8
- package/src/logging/agentOutput.ts +216 -0
- package/src/logging/formatter.ts +23 -4
- package/src/logging/logger.ts +2 -0
- package/src/logging/reader.ts +165 -1
- package/src/services/__tests__/BatchMergeService.test.ts +34 -14
- package/src/services/codingAgentResolver.ts +12 -5
- package/src/services/dependency-installer.ts +2 -2
- package/src/shared/codingAgentConstants.ts +73 -0
- package/src/utils/bun-runtime.ts +29 -0
- package/src/utils/session/common.ts +28 -0
- package/src/utils/session/parsers/claude.ts +79 -29
- package/src/utils/session/parsers/codex.ts +49 -26
- package/src/utils/session/parsers/gemini.ts +46 -5
- package/src/utils/session/parsers/opencode.ts +46 -5
- package/src/utils/session/types.ts +4 -0
- package/src/worktree.ts +28 -15
package/src/launcher.ts
CHANGED
|
@@ -9,7 +9,14 @@ import { execa } from "execa";
|
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import type { CodingAgent, CodingAgentLaunchOptions } from "./types/tools.js";
|
|
11
11
|
import { createLogger } from "./logging/logger.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
runAgentWithPty,
|
|
14
|
+
shouldCaptureAgentOutput,
|
|
15
|
+
} from "./logging/agentOutput.js";
|
|
16
|
+
import {
|
|
17
|
+
parsePackageCommand,
|
|
18
|
+
resolveVersionSuffix,
|
|
19
|
+
} from "./utils/npmRegistry.js";
|
|
13
20
|
import { writeTerminalLine } from "./utils/terminal.js";
|
|
14
21
|
|
|
15
22
|
const logger = createLogger({ category: "launcher" });
|
|
@@ -125,14 +132,32 @@ export async function launchCodingAgent(
|
|
|
125
132
|
...(options.sharedEnv ?? {}),
|
|
126
133
|
...(agent.env ?? {}),
|
|
127
134
|
};
|
|
135
|
+
const workingDir = options.cwd ?? process.cwd();
|
|
136
|
+
const captureOutput = shouldCaptureAgentOutput(env);
|
|
128
137
|
|
|
129
138
|
// execa共通オプション(cwdがundefinedの場合は含めない)
|
|
130
139
|
const execaOptions = {
|
|
131
140
|
stdio: "inherit" as const,
|
|
132
|
-
...(
|
|
141
|
+
...(workingDir ? { cwd: workingDir } : {}),
|
|
133
142
|
env,
|
|
134
143
|
};
|
|
135
144
|
|
|
145
|
+
const runWithCapture = async (command: string, commandArgs: string[]) => {
|
|
146
|
+
const result = await runAgentWithPty({
|
|
147
|
+
command,
|
|
148
|
+
args: commandArgs,
|
|
149
|
+
cwd: workingDir,
|
|
150
|
+
env,
|
|
151
|
+
agentId: agent.id,
|
|
152
|
+
});
|
|
153
|
+
if (result.signal !== null && result.signal !== undefined) {
|
|
154
|
+
throw new Error(`Coding agent terminated by signal ${result.signal}`);
|
|
155
|
+
}
|
|
156
|
+
if (result.exitCode !== null && result.exitCode !== 0) {
|
|
157
|
+
throw new Error(`Coding agent exited with code ${result.exitCode}`);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
136
161
|
logger.info(
|
|
137
162
|
{
|
|
138
163
|
agentId: agent.id,
|
|
@@ -146,7 +171,11 @@ export async function launchCodingAgent(
|
|
|
146
171
|
switch (agent.type) {
|
|
147
172
|
case "path": {
|
|
148
173
|
// 絶対パスで直接実行
|
|
149
|
-
|
|
174
|
+
if (captureOutput) {
|
|
175
|
+
await runWithCapture(agent.command, args);
|
|
176
|
+
} else {
|
|
177
|
+
await execa(agent.command, args, execaOptions);
|
|
178
|
+
}
|
|
150
179
|
logger.info({ agentId: agent.id }, "Coding agent completed (path)");
|
|
151
180
|
break;
|
|
152
181
|
}
|
|
@@ -154,9 +183,12 @@ export async function launchCodingAgent(
|
|
|
154
183
|
case "bunx": {
|
|
155
184
|
// bunx経由でパッケージ実行
|
|
156
185
|
// バージョン指定がある場合はパッケージ名に付与
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
const { packageName, version: embeddedVersion } = parsePackageCommand(
|
|
187
|
+
agent.command,
|
|
188
|
+
);
|
|
189
|
+
const selectedVersion = options.version ?? embeddedVersion ?? "latest";
|
|
190
|
+
const versionSuffix = resolveVersionSuffix(selectedVersion);
|
|
191
|
+
const packageWithVersion = `${packageName}${versionSuffix}`;
|
|
160
192
|
|
|
161
193
|
// FR-072: Log version information
|
|
162
194
|
if (selectedVersion === "installed") {
|
|
@@ -167,7 +199,12 @@ export async function launchCodingAgent(
|
|
|
167
199
|
writeTerminalLine(chalk.cyan(` 🔄 Using bunx ${packageWithVersion}`));
|
|
168
200
|
|
|
169
201
|
// bunx [package@version] [args...]
|
|
170
|
-
await
|
|
202
|
+
const bunxCommand = captureOutput ? await resolveCommand("bunx") : "bunx";
|
|
203
|
+
if (captureOutput) {
|
|
204
|
+
await runWithCapture(bunxCommand, [packageWithVersion, ...args]);
|
|
205
|
+
} else {
|
|
206
|
+
await execa("bunx", [packageWithVersion, ...args], execaOptions);
|
|
207
|
+
}
|
|
171
208
|
logger.info(
|
|
172
209
|
{ agentId: agent.id, version: selectedVersion },
|
|
173
210
|
"Coding agent completed (bunx)",
|
|
@@ -178,7 +215,11 @@ export async function launchCodingAgent(
|
|
|
178
215
|
case "command": {
|
|
179
216
|
// PATH解決 → 実行
|
|
180
217
|
const resolvedPath = await resolveCommand(agent.command);
|
|
181
|
-
|
|
218
|
+
if (captureOutput) {
|
|
219
|
+
await runWithCapture(resolvedPath, args);
|
|
220
|
+
} else {
|
|
221
|
+
await execa(resolvedPath, args, execaOptions);
|
|
222
|
+
}
|
|
182
223
|
logger.info({ agentId: agent.id }, "Coding agent completed (command)");
|
|
183
224
|
break;
|
|
184
225
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as pty from "node-pty";
|
|
2
|
+
import type { IPty } from "node-pty";
|
|
3
|
+
import { createLogger } from "./logger.js";
|
|
4
|
+
import { resolveLogDir } from "./reader.js";
|
|
5
|
+
import { getTerminalStreams } from "../utils/terminal.js";
|
|
6
|
+
|
|
7
|
+
export const CAPTURE_AGENT_OUTPUT_ENV = "GWT_CAPTURE_AGENT_OUTPUT";
|
|
8
|
+
|
|
9
|
+
export function shouldCaptureAgentOutput(
|
|
10
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
11
|
+
): boolean {
|
|
12
|
+
const raw = env[CAPTURE_AGENT_OUTPUT_ENV];
|
|
13
|
+
if (raw === undefined) {
|
|
14
|
+
// Default to false to avoid PTY stdin/stdout conflicts with OpenTUI.
|
|
15
|
+
// Set GWT_CAPTURE_AGENT_OUTPUT=true to enable agent output logging.
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return normalized === "true" || normalized === "1";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line no-control-regex
|
|
26
|
+
const ANSI_PATTERN = new RegExp("\\u001b\\[[0-9;]*[A-Za-z]", "g");
|
|
27
|
+
|
|
28
|
+
export function stripAnsi(value: string): string {
|
|
29
|
+
return value.replace(ANSI_PATTERN, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AgentOutputLineBuffer {
|
|
33
|
+
push: (chunk: string) => void;
|
|
34
|
+
flush: () => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createAgentOutputLineBuffer(
|
|
38
|
+
onLine: (line: string) => void,
|
|
39
|
+
): AgentOutputLineBuffer {
|
|
40
|
+
let buffer = "";
|
|
41
|
+
|
|
42
|
+
const push = (chunk: string) => {
|
|
43
|
+
const normalized = chunk.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
44
|
+
buffer += normalized;
|
|
45
|
+
while (true) {
|
|
46
|
+
const index = buffer.indexOf("\n");
|
|
47
|
+
if (index === -1) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
const line = buffer.slice(0, index);
|
|
51
|
+
buffer = buffer.slice(index + 1);
|
|
52
|
+
onLine(line);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const flush = () => {
|
|
57
|
+
if (!buffer) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const line = buffer;
|
|
61
|
+
buffer = "";
|
|
62
|
+
onLine(line);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return { push, flush };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface AgentOutputCaptureOptions {
|
|
69
|
+
command: string;
|
|
70
|
+
args: string[];
|
|
71
|
+
cwd: string;
|
|
72
|
+
env: NodeJS.ProcessEnv;
|
|
73
|
+
agentId: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AgentOutputCaptureResult {
|
|
77
|
+
exitCode: number | null;
|
|
78
|
+
signal?: number | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const normalizeEnv = (env: NodeJS.ProcessEnv): Record<string, string> =>
|
|
82
|
+
Object.fromEntries(
|
|
83
|
+
Object.entries(env).filter(
|
|
84
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const getTerminalSize = (terminal: ReturnType<typeof getTerminalStreams>) => {
|
|
89
|
+
const stdout = terminal.stdout as
|
|
90
|
+
| (NodeJS.WriteStream & { columns?: number; rows?: number })
|
|
91
|
+
| undefined;
|
|
92
|
+
const cols = stdout?.columns ?? process.stdout.columns ?? 80;
|
|
93
|
+
const rows = stdout?.rows ?? process.stdout.rows ?? 24;
|
|
94
|
+
return { cols, rows };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export async function runAgentWithPty(
|
|
98
|
+
options: AgentOutputCaptureOptions,
|
|
99
|
+
): Promise<AgentOutputCaptureResult> {
|
|
100
|
+
const terminal = getTerminalStreams();
|
|
101
|
+
const { cols, rows } = getTerminalSize(terminal);
|
|
102
|
+
const logDir = resolveLogDir(options.cwd);
|
|
103
|
+
const stdoutLogger = createLogger({
|
|
104
|
+
category: "agent.stdout",
|
|
105
|
+
logDir,
|
|
106
|
+
base: { agentId: options.agentId },
|
|
107
|
+
});
|
|
108
|
+
const stderrLogger = createLogger({
|
|
109
|
+
category: "agent.stderr",
|
|
110
|
+
logDir,
|
|
111
|
+
base: { agentId: options.agentId },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const normalizedEnv = normalizeEnv(options.env);
|
|
115
|
+
const ptyProcess: IPty = pty.spawn(options.command, options.args, {
|
|
116
|
+
name: process.env.TERM ?? "xterm-256color",
|
|
117
|
+
cols,
|
|
118
|
+
rows,
|
|
119
|
+
cwd: options.cwd,
|
|
120
|
+
env: normalizedEnv,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const lineBuffer = createAgentOutputLineBuffer((line) => {
|
|
124
|
+
const cleaned = stripAnsi(line).trimEnd();
|
|
125
|
+
if (!cleaned) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
stdoutLogger.info(cleaned);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const stdout = terminal.stdout;
|
|
132
|
+
const writeToTerminal =
|
|
133
|
+
stdout && typeof stdout.write === "function"
|
|
134
|
+
? stdout.write.bind(stdout)
|
|
135
|
+
: null;
|
|
136
|
+
|
|
137
|
+
ptyProcess.onData((data) => {
|
|
138
|
+
if (writeToTerminal) {
|
|
139
|
+
try {
|
|
140
|
+
writeToTerminal(data);
|
|
141
|
+
} catch {
|
|
142
|
+
// Ignore terminal write errors.
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
lineBuffer.push(data);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const stdin = terminal.stdin;
|
|
149
|
+
const handleInput = (chunk: Buffer | string) => {
|
|
150
|
+
const data = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
151
|
+
ptyProcess.write(data);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const stdinWasRaw =
|
|
155
|
+
stdin &&
|
|
156
|
+
typeof (stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw ===
|
|
157
|
+
"boolean"
|
|
158
|
+
? (stdin as NodeJS.ReadStream & { isRaw?: boolean }).isRaw
|
|
159
|
+
: undefined;
|
|
160
|
+
|
|
161
|
+
if (stdin && typeof stdin.on === "function") {
|
|
162
|
+
if (stdin.isTTY && typeof stdin.setRawMode === "function") {
|
|
163
|
+
try {
|
|
164
|
+
stdin.setRawMode(true);
|
|
165
|
+
} catch {
|
|
166
|
+
// Ignore raw mode errors.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (typeof stdin.resume === "function") {
|
|
170
|
+
stdin.resume();
|
|
171
|
+
}
|
|
172
|
+
stdin.on("data", handleInput);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const handleResize = () => {
|
|
176
|
+
const next = getTerminalSize(terminal);
|
|
177
|
+
try {
|
|
178
|
+
ptyProcess.resize(next.cols, next.rows);
|
|
179
|
+
} catch {
|
|
180
|
+
// Ignore resize errors.
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
if (process.stdout && typeof process.stdout.on === "function") {
|
|
184
|
+
process.stdout.on("resize", handleResize);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return await new Promise<AgentOutputCaptureResult>((resolve) => {
|
|
188
|
+
ptyProcess.onExit(({ exitCode, signal }) => {
|
|
189
|
+
lineBuffer.flush();
|
|
190
|
+
if (stdin && typeof stdin.off === "function") {
|
|
191
|
+
stdin.off("data", handleInput);
|
|
192
|
+
}
|
|
193
|
+
if (stdin && typeof stdin.pause === "function") {
|
|
194
|
+
stdin.pause();
|
|
195
|
+
}
|
|
196
|
+
if (stdin && stdin.isTTY && typeof stdin.setRawMode === "function") {
|
|
197
|
+
try {
|
|
198
|
+
stdin.setRawMode(Boolean(stdinWasRaw));
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore raw mode restore errors.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (process.stdout && typeof process.stdout.off === "function") {
|
|
204
|
+
process.stdout.off("resize", handleResize);
|
|
205
|
+
}
|
|
206
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
207
|
+
stderrLogger.error(
|
|
208
|
+
{ exitCode, signal },
|
|
209
|
+
"Agent exited with non-zero code",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const normalizedSignal = signal ?? null;
|
|
213
|
+
resolve({ exitCode, signal: normalizedSignal });
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
package/src/logging/formatter.ts
CHANGED
|
@@ -19,17 +19,36 @@ const LEVEL_LABELS: Record<number, string> = {
|
|
|
19
19
|
60: "FATAL",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const LOCAL_TIME_FORMATTER = new Intl.DateTimeFormat(undefined, {
|
|
23
|
+
hour: "2-digit",
|
|
24
|
+
minute: "2-digit",
|
|
25
|
+
second: "2-digit",
|
|
26
|
+
hour12: false,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const formatLocalTimeParts = (date: Date): string => {
|
|
30
|
+
const parts = LOCAL_TIME_FORMATTER.formatToParts(date);
|
|
31
|
+
const get = (type: Intl.DateTimeFormatPartTypes) =>
|
|
32
|
+
parts.find((part) => part.type === type)?.value;
|
|
33
|
+
const hour = get("hour");
|
|
34
|
+
const minute = get("minute");
|
|
35
|
+
const second = get("second");
|
|
36
|
+
|
|
37
|
+
if (!hour || !minute || !second) {
|
|
38
|
+
return LOCAL_TIME_FORMATTER.format(date);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `${hour}:${minute}:${second}`;
|
|
42
|
+
};
|
|
43
|
+
|
|
22
44
|
const formatTimeLabel = (
|
|
23
45
|
value: unknown,
|
|
24
46
|
): { label: string; timestamp: number | null } => {
|
|
25
47
|
if (typeof value === "string" || typeof value === "number") {
|
|
26
48
|
const date = new Date(value);
|
|
27
49
|
if (!Number.isNaN(date.getTime())) {
|
|
28
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
29
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
30
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
31
50
|
return {
|
|
32
|
-
label:
|
|
51
|
+
label: formatLocalTimeParts(date),
|
|
33
52
|
timestamp: date.getTime(),
|
|
34
53
|
};
|
|
35
54
|
}
|
package/src/logging/logger.ts
CHANGED
|
@@ -59,6 +59,7 @@ export function createLogger(config: LoggerConfig = {}): Logger {
|
|
|
59
59
|
const destinationStream = pino.destination({
|
|
60
60
|
dest: destination,
|
|
61
61
|
sync: true,
|
|
62
|
+
append: true,
|
|
62
63
|
});
|
|
63
64
|
return pino(options, destinationStream);
|
|
64
65
|
}
|
|
@@ -89,6 +90,7 @@ export function createLogger(config: LoggerConfig = {}): Logger {
|
|
|
89
90
|
const destinationStream = pino.destination({
|
|
90
91
|
dest: destination,
|
|
91
92
|
sync: false,
|
|
93
|
+
append: true,
|
|
92
94
|
});
|
|
93
95
|
return pino(options, destinationStream);
|
|
94
96
|
}
|
package/src/logging/reader.ts
CHANGED
|
@@ -9,6 +9,26 @@ export interface LogFileInfo {
|
|
|
9
9
|
mtimeMs: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export type LogTargetReason =
|
|
13
|
+
| "worktree"
|
|
14
|
+
| "worktree-inaccessible"
|
|
15
|
+
| "current-working-directory"
|
|
16
|
+
| "working-directory"
|
|
17
|
+
| "working-directory-fallback"
|
|
18
|
+
| "no-worktree";
|
|
19
|
+
|
|
20
|
+
export interface LogTargetBranch {
|
|
21
|
+
name: string;
|
|
22
|
+
isCurrent?: boolean;
|
|
23
|
+
worktree?: { path: string; isAccessible?: boolean } | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LogTargetResolution {
|
|
27
|
+
logDir: string | null;
|
|
28
|
+
sourcePath: string | null;
|
|
29
|
+
reason: LogTargetReason;
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
const LOG_FILENAME_PATTERN = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
|
|
13
33
|
|
|
14
34
|
export function resolveLogDir(cwd: string = process.cwd()): string {
|
|
@@ -16,6 +36,46 @@ export function resolveLogDir(cwd: string = process.cwd()): string {
|
|
|
16
36
|
return path.join(os.homedir(), ".gwt", "logs", cwdBase);
|
|
17
37
|
}
|
|
18
38
|
|
|
39
|
+
export function resolveLogTarget(
|
|
40
|
+
branch: LogTargetBranch | null,
|
|
41
|
+
workingDirectory: string = process.cwd(),
|
|
42
|
+
): LogTargetResolution {
|
|
43
|
+
if (!branch) {
|
|
44
|
+
return {
|
|
45
|
+
logDir: resolveLogDir(workingDirectory),
|
|
46
|
+
sourcePath: workingDirectory,
|
|
47
|
+
reason: "working-directory",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const worktreePath = branch.worktree?.path;
|
|
52
|
+
if (worktreePath) {
|
|
53
|
+
const accessible = branch.worktree?.isAccessible !== false;
|
|
54
|
+
if (accessible) {
|
|
55
|
+
return {
|
|
56
|
+
logDir: resolveLogDir(worktreePath),
|
|
57
|
+
sourcePath: worktreePath,
|
|
58
|
+
reason: "worktree",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
logDir: null,
|
|
63
|
+
sourcePath: worktreePath,
|
|
64
|
+
reason: "worktree-inaccessible",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (branch.isCurrent) {
|
|
69
|
+
return {
|
|
70
|
+
logDir: resolveLogDir(workingDirectory),
|
|
71
|
+
sourcePath: workingDirectory,
|
|
72
|
+
reason: "current-working-directory",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { logDir: null, sourcePath: null, reason: "no-worktree" };
|
|
77
|
+
}
|
|
78
|
+
|
|
19
79
|
export function buildLogFilePath(logDir: string, date: string): string {
|
|
20
80
|
return path.join(logDir, `${date}.jsonl`);
|
|
21
81
|
}
|
|
@@ -56,7 +116,7 @@ export async function listLogFiles(logDir: string): Promise<LogFileInfo[]> {
|
|
|
56
116
|
}
|
|
57
117
|
}
|
|
58
118
|
|
|
59
|
-
return files.sort((a, b) => b.
|
|
119
|
+
return files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
60
120
|
} catch (error) {
|
|
61
121
|
const err = error as NodeJS.ErrnoException;
|
|
62
122
|
if (err.code === "ENOENT") {
|
|
@@ -66,6 +126,69 @@ export async function listLogFiles(logDir: string): Promise<LogFileInfo[]> {
|
|
|
66
126
|
}
|
|
67
127
|
}
|
|
68
128
|
|
|
129
|
+
const getLatestLogMtimeWithContent = async (
|
|
130
|
+
logDir: string,
|
|
131
|
+
): Promise<number | null> => {
|
|
132
|
+
const files = await listLogFiles(logDir);
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
const lines = await readLogFileLines(file.path);
|
|
135
|
+
if (lines.length > 0) {
|
|
136
|
+
return file.mtimeMs;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export async function selectLogTargetByRecency(
|
|
143
|
+
primary: LogTargetResolution,
|
|
144
|
+
fallback: LogTargetResolution,
|
|
145
|
+
): Promise<LogTargetResolution> {
|
|
146
|
+
if (!primary.logDir || !primary.sourcePath) {
|
|
147
|
+
return primary;
|
|
148
|
+
}
|
|
149
|
+
if (!fallback.logDir || !fallback.sourcePath) {
|
|
150
|
+
return primary;
|
|
151
|
+
}
|
|
152
|
+
if (primary.logDir === fallback.logDir) {
|
|
153
|
+
return primary;
|
|
154
|
+
}
|
|
155
|
+
if (primary.reason !== "worktree") {
|
|
156
|
+
return primary;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const [primaryMtime, fallbackMtime] = await Promise.all([
|
|
160
|
+
getLatestLogMtimeWithContent(primary.logDir),
|
|
161
|
+
getLatestLogMtimeWithContent(fallback.logDir),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
fallbackMtime !== null &&
|
|
166
|
+
(primaryMtime === null || fallbackMtime > primaryMtime)
|
|
167
|
+
) {
|
|
168
|
+
return {
|
|
169
|
+
...fallback,
|
|
170
|
+
reason: "working-directory-fallback",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return primary;
|
|
174
|
+
}
|
|
175
|
+
export async function clearLogFiles(logDir: string): Promise<number> {
|
|
176
|
+
const files = await listLogFiles(logDir);
|
|
177
|
+
let cleared = 0;
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
try {
|
|
180
|
+
await fs.truncate(file.path, 0);
|
|
181
|
+
cleared += 1;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const err = error as NodeJS.ErrnoException;
|
|
184
|
+
if (err.code !== "ENOENT") {
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return cleared;
|
|
190
|
+
}
|
|
191
|
+
|
|
69
192
|
export async function listRecentLogFiles(
|
|
70
193
|
logDir: string,
|
|
71
194
|
days = 7,
|
|
@@ -74,3 +197,44 @@ export async function listRecentLogFiles(
|
|
|
74
197
|
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
75
198
|
return files.filter((file) => file.mtimeMs >= cutoff);
|
|
76
199
|
}
|
|
200
|
+
|
|
201
|
+
export interface LogReadResult {
|
|
202
|
+
date: string;
|
|
203
|
+
lines: string[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function readLogLinesForDate(
|
|
207
|
+
logDir: string,
|
|
208
|
+
preferredDate: string,
|
|
209
|
+
): Promise<LogReadResult | null> {
|
|
210
|
+
const files = await listLogFiles(logDir);
|
|
211
|
+
if (files.length === 0) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const ordered: LogFileInfo[] = [];
|
|
216
|
+
const preferred = files.find((file) => file.date === preferredDate);
|
|
217
|
+
if (preferred) {
|
|
218
|
+
ordered.push(preferred);
|
|
219
|
+
}
|
|
220
|
+
for (const file of files) {
|
|
221
|
+
if (preferred && file.date === preferred.date) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
ordered.push(file);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const file of ordered) {
|
|
228
|
+
const lines = await readLogFileLines(file.path);
|
|
229
|
+
if (lines.length > 0) {
|
|
230
|
+
return { date: file.date, lines };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const fallback = files[0];
|
|
235
|
+
if (!fallback) {
|
|
236
|
+
return { date: preferredDate, lines: [] };
|
|
237
|
+
}
|
|
238
|
+
const fallbackDate = preferred?.date ?? fallback.date;
|
|
239
|
+
return { date: fallbackDate, lines: [] };
|
|
240
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test";
|
|
2
2
|
import { BatchMergeService } from "../BatchMergeService";
|
|
3
|
-
import type { BatchMergeConfig } from "../../ui/types";
|
|
3
|
+
import type { BatchMergeConfig, BatchMergeProgress } from "../../cli/ui/types";
|
|
4
4
|
|
|
5
5
|
// Mock git module
|
|
6
6
|
mock.module("../../git", () => ({
|
|
@@ -235,7 +235,9 @@ describe("BatchMergeService", () => {
|
|
|
235
235
|
(
|
|
236
236
|
worktree.generateWorktreePath as ReturnType<typeof mock>
|
|
237
237
|
).mockResolvedValue("/repo/.worktrees/feature-b");
|
|
238
|
-
(worktree.createWorktree as ReturnType<typeof mock>).mockResolvedValue(
|
|
238
|
+
(worktree.createWorktree as ReturnType<typeof mock>).mockResolvedValue(
|
|
239
|
+
undefined,
|
|
240
|
+
);
|
|
239
241
|
|
|
240
242
|
const worktreePath = await service.ensureWorktree("feature/b");
|
|
241
243
|
|
|
@@ -271,7 +273,9 @@ describe("BatchMergeService", () => {
|
|
|
271
273
|
});
|
|
272
274
|
|
|
273
275
|
it("should successfully merge without conflicts", async () => {
|
|
274
|
-
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
276
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
277
|
+
undefined,
|
|
278
|
+
);
|
|
275
279
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
276
280
|
false,
|
|
277
281
|
);
|
|
@@ -293,7 +297,7 @@ describe("BatchMergeService", () => {
|
|
|
293
297
|
new Error("Merge conflict"),
|
|
294
298
|
);
|
|
295
299
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(true);
|
|
296
|
-
(git.abortMerge as ReturnType<typeof mock>).mockResolvedValue();
|
|
300
|
+
(git.abortMerge as ReturnType<typeof mock>).mockResolvedValue(undefined);
|
|
297
301
|
|
|
298
302
|
const status = await service.mergeBranch("feature/a", "main", config);
|
|
299
303
|
|
|
@@ -339,11 +343,13 @@ describe("BatchMergeService", () => {
|
|
|
339
343
|
});
|
|
340
344
|
|
|
341
345
|
it("should rollback with resetToHead after successful dry-run merge", async () => {
|
|
342
|
-
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
346
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
347
|
+
undefined,
|
|
348
|
+
);
|
|
343
349
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
344
350
|
false,
|
|
345
351
|
);
|
|
346
|
-
(git.resetToHead as ReturnType<typeof mock>).mockResolvedValue();
|
|
352
|
+
(git.resetToHead as ReturnType<typeof mock>).mockResolvedValue(undefined);
|
|
347
353
|
|
|
348
354
|
const status = await service.mergeBranch(
|
|
349
355
|
"feature/a",
|
|
@@ -368,7 +374,7 @@ describe("BatchMergeService", () => {
|
|
|
368
374
|
new Error("CONFLICT (content)"),
|
|
369
375
|
);
|
|
370
376
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(true);
|
|
371
|
-
(git.abortMerge as ReturnType<typeof mock>).mockResolvedValue();
|
|
377
|
+
(git.abortMerge as ReturnType<typeof mock>).mockResolvedValue(undefined);
|
|
372
378
|
|
|
373
379
|
const status = await service.mergeBranch(
|
|
374
380
|
"feature/a",
|
|
@@ -405,14 +411,18 @@ describe("BatchMergeService", () => {
|
|
|
405
411
|
});
|
|
406
412
|
|
|
407
413
|
it("should push successfully after merge when autoPush is enabled", async () => {
|
|
408
|
-
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
414
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
415
|
+
undefined,
|
|
416
|
+
);
|
|
409
417
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
410
418
|
false,
|
|
411
419
|
);
|
|
412
420
|
(git.getCurrentBranchName as ReturnType<typeof mock>).mockResolvedValue(
|
|
413
421
|
"feature/a",
|
|
414
422
|
);
|
|
415
|
-
(git.pushBranchToRemote as ReturnType<typeof mock>).mockResolvedValue(
|
|
423
|
+
(git.pushBranchToRemote as ReturnType<typeof mock>).mockResolvedValue(
|
|
424
|
+
undefined,
|
|
425
|
+
);
|
|
416
426
|
|
|
417
427
|
const status = await service.mergeBranch(
|
|
418
428
|
"feature/a",
|
|
@@ -431,7 +441,9 @@ describe("BatchMergeService", () => {
|
|
|
431
441
|
});
|
|
432
442
|
|
|
433
443
|
it("should handle push failure without failing merge", async () => {
|
|
434
|
-
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
444
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
445
|
+
undefined,
|
|
446
|
+
);
|
|
435
447
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
436
448
|
false,
|
|
437
449
|
);
|
|
@@ -455,7 +467,9 @@ describe("BatchMergeService", () => {
|
|
|
455
467
|
|
|
456
468
|
it("should not push when autoPush is false", async () => {
|
|
457
469
|
const noPushConfig = { ...autoPushConfig, autoPush: false };
|
|
458
|
-
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
470
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
471
|
+
undefined,
|
|
472
|
+
);
|
|
459
473
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
460
474
|
false,
|
|
461
475
|
);
|
|
@@ -482,7 +496,9 @@ describe("BatchMergeService", () => {
|
|
|
482
496
|
autoPush: false,
|
|
483
497
|
};
|
|
484
498
|
|
|
485
|
-
(git.fetchAllRemotes as ReturnType<typeof mock>).mockResolvedValue(
|
|
499
|
+
(git.fetchAllRemotes as ReturnType<typeof mock>).mockResolvedValue(
|
|
500
|
+
undefined,
|
|
501
|
+
);
|
|
486
502
|
(git.getRepositoryRoot as ReturnType<typeof mock>).mockResolvedValue(
|
|
487
503
|
"/repo",
|
|
488
504
|
);
|
|
@@ -492,8 +508,12 @@ describe("BatchMergeService", () => {
|
|
|
492
508
|
(worktree.generateWorktreePath as ReturnType<typeof mock>)
|
|
493
509
|
.mockResolvedValueOnce("/repo/.worktrees/feature-a")
|
|
494
510
|
.mockResolvedValueOnce("/repo/.worktrees/feature-b");
|
|
495
|
-
(worktree.createWorktree as ReturnType<typeof mock>).mockResolvedValue(
|
|
496
|
-
|
|
511
|
+
(worktree.createWorktree as ReturnType<typeof mock>).mockResolvedValue(
|
|
512
|
+
undefined,
|
|
513
|
+
);
|
|
514
|
+
(git.mergeFromBranch as ReturnType<typeof mock>).mockResolvedValue(
|
|
515
|
+
undefined,
|
|
516
|
+
);
|
|
497
517
|
(git.hasMergeConflict as ReturnType<typeof mock>).mockResolvedValue(
|
|
498
518
|
false,
|
|
499
519
|
);
|