Skip to content

Commit 9f0c318

Browse files
committed
Add stash support and unhide Explorer launches
- Add VCS stash list/create/apply/drop operations through server, contracts, and web RPCs - Preserve visible Windows Explorer/file-manager launches when process hiding is opted out - Update tests for the new stash flow and launcher behavior
1 parent 0d834cb commit 9f0c318

19 files changed

Lines changed: 2285 additions & 55 deletions

apps/server/src/git/GitWorkflowService.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ import {
3939
type GitPreparePullRequestThreadInput,
4040
type GitPreparePullRequestThreadResult,
4141
type GitPullRequestRefInput,
42+
type VcsApplyStashInput,
43+
type VcsApplyStashResult,
44+
type VcsCreateStashInput,
45+
type VcsCreateStashResult,
46+
type VcsDropStashInput,
47+
type VcsDropStashResult,
48+
type VcsListStashesInput,
49+
type VcsListStashesResult,
4250
type VcsPullInput,
4351
type VcsPullResult,
4452
type VcsRemoveWorktreeInput,
@@ -72,6 +80,18 @@ export interface GitWorkflowServiceShape {
7280
readonly pullCurrentBranch: (
7381
input: VcsPullInput,
7482
) => Effect.Effect<VcsPullResult, GitCommandError>;
83+
readonly listStashes: (
84+
input: VcsListStashesInput,
85+
) => Effect.Effect<VcsListStashesResult, GitCommandError>;
86+
readonly createStash: (
87+
input: VcsCreateStashInput,
88+
) => Effect.Effect<VcsCreateStashResult, GitCommandError>;
89+
readonly applyStash: (
90+
input: VcsApplyStashInput,
91+
) => Effect.Effect<VcsApplyStashResult, GitCommandError>;
92+
readonly dropStash: (
93+
input: VcsDropStashInput,
94+
) => Effect.Effect<VcsDropStashResult, GitCommandError>;
7595
readonly runStackedAction: (
7696
input: GitRunStackedActionInput,
7797
options?: GitRunStackedActionOptions,
@@ -354,6 +374,22 @@ export const make = Effect.fn("makeGitWorkflowService")(function* () {
354374
ensureGitCommand("GitWorkflowService.pullCurrentBranch", input.cwd).pipe(
355375
Effect.andThen(git.pullCurrentBranch(input)),
356376
),
377+
listStashes: (input) =>
378+
ensureGitCommand("GitWorkflowService.listStashes", input.cwd).pipe(
379+
Effect.andThen(git.listStashes(input)),
380+
),
381+
createStash: (input) =>
382+
ensureGitCommand("GitWorkflowService.createStash", input.cwd).pipe(
383+
Effect.andThen(git.createStash(input)),
384+
),
385+
applyStash: (input) =>
386+
ensureGitCommand("GitWorkflowService.applyStash", input.cwd).pipe(
387+
Effect.andThen(git.applyStash(input)),
388+
),
389+
dropStash: (input) =>
390+
ensureGitCommand("GitWorkflowService.dropStash", input.cwd).pipe(
391+
Effect.andThen(git.dropStash(input)),
392+
),
357393
runStackedAction: (input, options) =>
358394
ensureGit("GitWorkflowService.runStackedAction", input.cwd).pipe(
359395
Effect.andThen(gitManager.runStackedAction(input, options)),

apps/server/src/process/externalLauncher.test.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
519519
command: "C:\\Windows\\explorer.exe",
520520
args: [`/select,${filePath}`],
521521
shell: false,
522+
windowsHide: false,
522523
});
523524

524525
const launch3 = yield* resolveEditorLaunch({ cwd: dir, editor: "file-manager" }, "win32", {
@@ -529,6 +530,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
529530
command: "C:\\Windows\\explorer.exe",
530531
args: [dir],
531532
shell: false,
533+
windowsHide: false,
532534
});
533535

534536
const launch4 = yield* resolveEditorLaunch(
@@ -610,6 +612,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
610612
command: "C:\\Windows\\explorer.exe",
611613
args: [`/select,${filePath}`],
612614
shell: false,
615+
windowsHide: false,
613616
});
614617
}),
615618
);
@@ -631,6 +634,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
631634
command: "C:\\Windows\\explorer.exe",
632635
args: [dir],
633636
shell: false,
637+
windowsHide: false,
634638
});
635639
}),
636640
);
@@ -776,7 +780,7 @@ it.layer(NodeServices.layer)("launchEditorProcess", (it) => {
776780
}),
777781
);
778782

779-
it.effect("does not wrap args or use a shell when a launch opts out", () =>
783+
it.effect("does not wrap args, use a shell, or hide the window when a launch opts out", () =>
780784
Effect.gen(function* () {
781785
let spawnedCommand: ChildProcess.StandardCommand | undefined;
782786
const expectedArgs = ["/select,C:\\workspace\\src\\file.ts"];
@@ -797,22 +801,20 @@ it.layer(NodeServices.layer)("launchEditorProcess", (it) => {
797801
command: process.execPath,
798802
args: expectedArgs,
799803
shell: false,
804+
windowsHide: false,
800805
}).pipe(Effect.provide(spawnerLayer), Effect.result);
801806

802807
assertSuccess(result, undefined);
803808
assert.ok(spawnedCommand);
804809
assert.equal(spawnedCommand.command, process.execPath);
805810
assert.deepEqual(spawnedCommand.args, expectedArgs);
806-
assert.deepEqual(
807-
spawnedCommand.options,
808-
expectedHiddenWindowsOptions({
809-
detached: true,
810-
shell: false,
811-
stdin: "ignore",
812-
stdout: "ignore",
813-
stderr: "ignore",
814-
}),
815-
);
811+
assert.deepEqual(spawnedCommand.options, {
812+
detached: true,
813+
shell: false,
814+
stdin: "ignore",
815+
stdout: "ignore",
816+
stderr: "ignore",
817+
});
816818
}),
817819
);
818820

apps/server/src/process/externalLauncher.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ interface EditorLaunch {
3535
readonly command: string;
3636
readonly args: ReadonlyArray<string>;
3737
readonly shell?: boolean;
38+
readonly windowsHide?: boolean;
3839
}
3940

4041
interface ProcessLaunch {
4142
readonly command: string;
4243
readonly args: ReadonlyArray<string>;
4344
readonly options: ChildProcess.CommandOptions;
45+
readonly windowsHide?: boolean;
4446
}
4547

4648
interface TargetPathAndPosition {
@@ -241,6 +243,9 @@ const resolveWindowsFileManagerLaunch = Effect.fn("resolveWindowsFileManagerLaun
241243
command: resolveWindowsExplorerPath(env),
242244
args,
243245
shell: false,
246+
// `windowsHide` maps to SW_HIDE for GUI processes in libuv. Explorer
247+
// starts successfully in that state but never presents its window.
248+
windowsHide: false,
244249
};
245250
});
246251

@@ -400,7 +405,7 @@ const launchAndUnref = Effect.fn("externalLauncher.launchAndUnref")(function* (
400405
const command = ChildProcess.make(
401406
launch.command,
402407
launch.args,
403-
hideWindowsConsole(launch.options),
408+
launch.windowsHide === false ? launch.options : hideWindowsConsole(launch.options),
404409
);
405410

406411
yield* spawner.spawn(command).pipe(
@@ -432,13 +437,14 @@ export const launchEditorProcess = Effect.fn("externalLauncher.launchEditorProce
432437
{
433438
command: launch.command,
434439
args: isWin32 && shell ? launch.args.map((arg) => `"${arg}"`) : [...launch.args],
435-
options: hideWindowsConsole({
440+
options: {
436441
detached: true,
437442
shell,
438443
stdin: "ignore",
439444
stdout: "ignore",
440445
stderr: "ignore",
441-
}),
446+
},
447+
...(launch.windowsHide !== undefined ? { windowsHide: launch.windowsHide } : {}),
442448
},
443449
"failed to spawn detached process",
444450
);

apps/server/src/vcs/GitVcsDriver.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ import {
4040
type VcsListRefsResult,
4141
type VcsMergeRefInput,
4242
type VcsMergeRefResult,
43+
type VcsApplyStashInput,
44+
type VcsApplyStashResult,
45+
type VcsCreateStashInput,
46+
type VcsCreateStashResult,
47+
type VcsDropStashInput,
48+
type VcsDropStashResult,
49+
type VcsListStashesInput,
50+
type VcsListStashesResult,
4351
type VcsPullInput,
4452
type VcsPullResult,
4553
type VcsRemoveWorktreeInput,
@@ -251,6 +259,18 @@ export interface GitVcsDriverShape {
251259
readonly pullCurrentBranch: (
252260
input: VcsPullInput,
253261
) => Effect.Effect<VcsPullResult, GitCommandError>;
262+
readonly listStashes: (
263+
input: VcsListStashesInput,
264+
) => Effect.Effect<VcsListStashesResult, GitCommandError>;
265+
readonly createStash: (
266+
input: VcsCreateStashInput,
267+
) => Effect.Effect<VcsCreateStashResult, GitCommandError>;
268+
readonly applyStash: (
269+
input: VcsApplyStashInput,
270+
) => Effect.Effect<VcsApplyStashResult, GitCommandError>;
271+
readonly dropStash: (
272+
input: VcsDropStashInput,
273+
) => Effect.Effect<VcsDropStashResult, GitCommandError>;
254274
readonly createWorktree: (
255275
input: VcsCreateWorktreeInput,
256276
) => Effect.Effect<VcsCreateWorktreeResult, GitCommandError>;

0 commit comments

Comments
 (0)