Skip to content

Commit fc548a2

Browse files
committed
Experimental Windows support
1 parent b482bd1 commit fc548a2

File tree

5 files changed

+29
-15
lines changed

5 files changed

+29
-15
lines changed

src/profile-query-cli/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A command-line interface for querying Firefox Profiler profiles with persistent
99
- **Daemon process**: Long-running background process that loads a profile via `ProfileQuerier` and keeps it in memory
1010
- **Client process**: Short-lived process that sends commands to the daemon and prints results
1111

12-
**IPC:** Unix domain sockets with line-delimited JSON messages
12+
**IPC:** Unix domain sockets (named pipes on Windows) with line-delimited JSON messages
1313

1414
**Session storage:** `~/.pq/` (or `$PQ_SESSION_DIR` for development)
1515

@@ -94,17 +94,19 @@ npm publish
9494
```
9595
~/.pq/
9696
├── current # Symlink to current session socket
97-
├── <session-id>.sock # Unix domain socket for IPC
97+
├── <session-id>.sock # Unix domain socket for IPC (Unix only)
9898
├── <session-id>.json # Session metadata (PID, profile path, timestamps)
9999
└── <session-id>.log # Daemon logs (kept for debugging)
100100
```
101101
102+
On Windows, IPC uses a named pipe (`\\.\pipe\pq-<session-id>`) instead of a `.sock` file.
103+
102104
**Session metadata example:**
103105
104106
```json
105107
{
106108
"id": "abc123xyz",
107-
"socketPath": "/Users/user/.pq/abc123xyz.sock",
109+
"socketPath": "/Users/user/.pq/abc123xyz.sock", // or \\.\pipe\pq-abc123xyz on Windows
108110
"logPath": "/Users/user/.pq/abc123xyz.log",
109111
"pid": 12345,
110112
"profilePath": "/path/to/profile.json",
@@ -181,7 +183,7 @@ type ServerResponse =
181183
**Session validation (session.ts):**
182184
183185
- Check PID is running (`process.kill(pid, 0)`)
184-
- Check socket file exists
186+
- Check socket file exists (Unix only — named pipes on Windows are not filesystem files)
185187
- Auto-cleanup stale sessions
186188
187189
**Symlinks:**
@@ -194,7 +196,7 @@ type ServerResponse =
194196
**Implemented:**
195197
196198
- ✅ Persistent daemon with profile loading
197-
- ✅ Unix socket IPC
199+
- ✅ Unix socket IPC (named pipes on Windows)
198200
- ✅ Multiple concurrent sessions
199201
- ✅ Session management (current session, explicit session IDs)
200202
- ✅ Environment variable isolation (`PQ_SESSION_DIR`)

src/profile-query-cli/client.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ async function sendRawMessage(
5858
? getSocketPath(sessionDir, sessionId)
5959
: getCurrentSocketPath(sessionDir);
6060

61-
if (!socketPath || !fs.existsSync(socketPath)) {
61+
// On Windows, named pipes are not filesystem files so existsSync always returns false
62+
if (
63+
!socketPath ||
64+
(process.platform !== 'win32' && !fs.existsSync(socketPath))
65+
) {
6266
throw new Error(`Socket not found for session ${resolvedSessionId}`);
6367
}
6468

src/profile-query-cli/daemon.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Daemon process for pq.
3-
* Loads a profile and listens for commands on a Unix socket.
3+
* Loads a profile and listens for commands on a Unix socket (or named pipe on Windows).
44
*/
55

66
import * as net from 'net';
@@ -90,8 +90,8 @@ export class Daemon {
9090
// Create Unix socket server BEFORE loading the profile
9191
this.server = net.createServer((socket) => this.handleConnection(socket));
9292

93-
// Remove stale socket if it exists
94-
if (fs.existsSync(this.socketPath)) {
93+
// Remove stale socket if it exists (Unix only — named pipes on Windows are not filesystem files)
94+
if (process.platform !== 'win32' && fs.existsSync(this.socketPath)) {
9595
fs.unlinkSync(this.socketPath);
9696
}
9797

src/profile-query-cli/session.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ export function generateSessionId(): string {
2929

3030
/**
3131
* Get the socket path for a session.
32+
* On Windows, returns a named pipe path. On Unix, returns a .sock file path.
3233
*/
3334
export function getSocketPath(sessionDir: string, sessionId: string): string {
35+
if (process.platform === 'win32') {
36+
return `\\\\.\\pipe\\pq-${sessionId}`;
37+
}
3438
return path.join(sessionDir, `${sessionId}.sock`);
3539
}
3640

@@ -143,8 +147,8 @@ export function cleanupSession(sessionDir: string, sessionId: string): void {
143147
// Note: We intentionally don't delete the log file for debugging purposes
144148
// const logPath = getLogPath(sessionDir, sessionId);
145149

146-
// Remove socket file
147-
if (fs.existsSync(socketPath)) {
150+
// Remove socket file (Unix only — named pipes on Windows are not filesystem files)
151+
if (process.platform !== 'win32' && fs.existsSync(socketPath)) {
148152
fs.unlinkSync(socketPath);
149153
}
150154

@@ -181,8 +185,8 @@ export function validateSession(
181185
return null;
182186
}
183187

184-
// Check if socket exists
185-
if (!fs.existsSync(metadata.socketPath)) {
188+
// Check if socket exists (Unix only — named pipes on Windows are not filesystem files)
189+
if (process.platform !== 'win32' && !fs.existsSync(metadata.socketPath)) {
186190
// console.error(`Session ${sessionId} socket not found. Cleaning up.`);
187191
return null;
188192
}

src/profile-query-cli/tests/daemon-startup.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
pqFail,
1313
type PqTestContext,
1414
} from './utils';
15+
import { getSocketPath } from '../session';
1516

1617
describe('daemon startup (two-phase)', () => {
1718
let ctx: PqTestContext;
@@ -102,10 +103,13 @@ describe('daemon startup (two-phase)', () => {
102103
const sessionId = match![1];
103104

104105
// Verify both socket and metadata exist (validateSession checks both)
105-
const socketPath = join(ctx.sessionDir, `${sessionId}.sock`);
106+
const socketPath = getSocketPath(ctx.sessionDir, sessionId);
106107
const metadataPath = join(ctx.sessionDir, `${sessionId}.json`);
107108

108-
await expect(access(socketPath)).resolves.toBeUndefined();
109+
// Named pipes on Windows are not filesystem files, skip the access check there
110+
if (process.platform !== 'win32') {
111+
await expect(access(socketPath)).resolves.toBeUndefined();
112+
}
109113
await expect(access(metadataPath)).resolves.toBeUndefined();
110114

111115
// Process should be running (metadata contains PID)

0 commit comments

Comments
 (0)