mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
Merge pull request #667: fix(mux): give a discovered tmux session its pane's directory, not the server cwd
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
620e7728ec
3 files changed
+169
-7
No files matched your search
+117
-1
@@ -52,11 +52,24 @@ vi.mock('node:child_process', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
// Paths realpathSync should resolve differently than this host does, e.g. a
|
||||
// macOS `/tmp` -> `/private/tmp`. Hoisted so it survives vi.resetModules().
|
||||
const realpathOverrides = vi.hoisted(() => new Map<string, string>());
|
||||
|
||||
// Mock fs to avoid file I/O
|
||||
vi.mock('node:fs', async () => {
|
||||
const actual = await vi.importActual('node:fs');
|
||||
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
||||
const realpathSync = Object.assign(
|
||||
vi.fn((path: string, ...rest: unknown[]) =>
|
||||
realpathOverrides.has(String(path))
|
||||
? realpathOverrides.get(String(path))
|
||||
: (actual.realpathSync as (...args: unknown[]) => string)(path, ...rest)
|
||||
),
|
||||
{ native: actual.realpathSync.native }
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
realpathSync,
|
||||
existsSync: vi.fn().mockReturnValue(false),
|
||||
readFileSync: vi.fn(),
|
||||
mkdirSync: vi.fn(),
|
||||
@@ -903,6 +916,92 @@ describe('TmuxManager (unit)', () => {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('gives a discovered session the directory tmux reports for its pane, not the server cwd', async () => {
|
||||
mockedExecSync.mockImplementation((cmd: string) => {
|
||||
if (typeof cmd === 'string' && cmd.includes(' list-panes ')) {
|
||||
return [
|
||||
'codeman-aaaa1111|101|0|||/home/user/project-a',
|
||||
// A hand-split session answers with the pane tmux lists first.
|
||||
'codeman-bbbb2222|102|0|||/home/user/project-b',
|
||||
'codeman-bbbb2222|103|0|||/home/user/project-b/sub',
|
||||
// Set by whoever owns the pane, so it must pass the same check as any workingDir.
|
||||
'codeman-cccc3333|104|0|||/home/user/$(touch pwned)',
|
||||
// A tmux that reported no path.
|
||||
'codeman-dddd4444|105|0|||',
|
||||
// The launch directory every pane starts in; a remote or docker pane never leaves it.
|
||||
'codeman-eeee5555|106|0|||/tmp',
|
||||
// A directory deleted while its pane ran, as tmux 3.4 on Linux reports it. isValidPath()
|
||||
// rejects it for the parentheses; a relaxed path pattern must not adopt a dead directory.
|
||||
'codeman-abcd7777|108|0|||/home/user/gone (deleted)',
|
||||
// Last row, path ending in a space: the output must not be trimmed.
|
||||
'codeman-ffff6666|107|0|||/home/user/trailing ',
|
||||
].join('\n');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
|
||||
const nonTestManager = new NonTestTmuxManager();
|
||||
|
||||
try {
|
||||
const { discovered } = await nonTestManager.reconcileSessions();
|
||||
expect(discovered).toEqual([
|
||||
'restored-aaaa1111',
|
||||
'restored-bbbb2222',
|
||||
'restored-cccc3333',
|
||||
'restored-dddd4444',
|
||||
'restored-eeee5555',
|
||||
'restored-abcd7777',
|
||||
'restored-ffff6666',
|
||||
]);
|
||||
const dirs = Object.fromEntries(nonTestManager.getSessions().map((s) => [s.muxName, s.workingDir]));
|
||||
expect(dirs).toEqual({
|
||||
'codeman-aaaa1111': '/home/user/project-a',
|
||||
'codeman-bbbb2222': '/home/user/project-b',
|
||||
'codeman-cccc3333': process.cwd(),
|
||||
'codeman-dddd4444': process.cwd(),
|
||||
'codeman-eeee5555': process.cwd(),
|
||||
'codeman-abcd7777': process.cwd(),
|
||||
'codeman-ffff6666': '/home/user/trailing ',
|
||||
});
|
||||
|
||||
// The directory comes from the same list-panes call, as its last field.
|
||||
const listCalls = mockedExecSync.mock.calls
|
||||
.map(([cmd]) => cmd)
|
||||
.filter((cmd): cmd is string => typeof cmd === 'string' && cmd.includes(' list-panes '));
|
||||
expect(listCalls).toHaveLength(1);
|
||||
expect(listCalls[0]).toContain("|#{pane_current_path}'");
|
||||
} finally {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('treats the resolved spelling of the launch directory as no answer too (macOS /private/tmp)', async () => {
|
||||
// On macOS /tmp is a symlink and a pane reports its process cwd resolved.
|
||||
realpathOverrides.set('/tmp', '/private/tmp');
|
||||
mockedExecSync.mockImplementation((cmd: string) => {
|
||||
if (typeof cmd === 'string' && cmd.includes(' list-panes ')) {
|
||||
return ['codeman-aaaa1111|101|0|||/private/tmp', 'codeman-bbbb2222|102|0|||/tmp'].join('\n');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
try {
|
||||
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
|
||||
const nonTestManager = new NonTestTmuxManager();
|
||||
try {
|
||||
await nonTestManager.reconcileSessions();
|
||||
const dirs = Object.fromEntries(nonTestManager.getSessions().map((s) => [s.muxName, s.workingDir]));
|
||||
expect(dirs).toEqual({
|
||||
'codeman-aaaa1111': process.cwd(),
|
||||
'codeman-bbbb2222': process.cwd(),
|
||||
});
|
||||
} finally {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
} finally {
|
||||
realpathOverrides.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1013,6 +1112,23 @@ describe('parsePaneRows', () => {
|
||||
expect(row.dead).toBeUndefined();
|
||||
});
|
||||
|
||||
it('reads the pane directory from the last field', () => {
|
||||
const [row] = parsePaneRows('codeman-aaaa|1234|0|||/home/user/project');
|
||||
expect(row.pid).toBe(1234);
|
||||
expect(row.dead).toBe(false);
|
||||
expect(row.currentPath).toBe('/home/user/project');
|
||||
});
|
||||
|
||||
it('keeps a pane directory that contains the separator whole', () => {
|
||||
const [row] = parsePaneRows('codeman-aaaa|1234|0|||/home/user/a|b');
|
||||
expect(row.currentPath).toBe('/home/user/a|b');
|
||||
});
|
||||
|
||||
it('leaves the pane directory undefined when tmux reported none', () => {
|
||||
expect(parsePaneRows('codeman-aaaa|1234')[0].currentPath).toBeUndefined();
|
||||
expect(parsePaneRows('codeman-aaaa|1234|0|||')[0].currentPath).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns one row per pane of a split session, in tmux order', () => {
|
||||
const rows = parsePaneRows('codeman-aaaa|100|0|||\ncodeman-aaaa|200|1|0|');
|
||||
expect(rows.map((row) => row.pid)).toEqual([100, 200]);
|
||||
|
||||
Reference in new issue
Block a user