mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
feat: add macOS compatibility with optional Playwright
This commit is contained in:
@@ -7,6 +7,7 @@ const webDir = join(rootDir, "pi-web");
|
||||
const requiredProfilePaths = [
|
||||
join(rootDir, "config", "settings.default.json"),
|
||||
join(rootDir, "config", "mcp.default.json"),
|
||||
join(rootDir, "config", "mcp.macos.default.json"),
|
||||
join(rootDir, "config", "models.example.json"),
|
||||
join(rootDir, "config", "subagents.default.json"),
|
||||
join(rootDir, "config", "subagent-tool-description.default.md"),
|
||||
@@ -27,7 +28,13 @@ const packages = [
|
||||
];
|
||||
|
||||
function readJson(path) {
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(`ERROR: Invalid JSON file ${path}: ${message}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const webPackage = readJson(join(webDir, "package.json"));
|
||||
|
||||
@@ -66,15 +66,19 @@ if (!npmCliPath) {
|
||||
console.error("Run the managed profile check through npm: npm run check:profile");
|
||||
process.exit(1);
|
||||
}
|
||||
const playwrightResult = spawnSync(
|
||||
process.execPath,
|
||||
[npmCliPath, "exec", "--yes", "--package=@playwright/mcp@0.0.78", "--", "playwright-mcp", "--version"],
|
||||
{ cwd: rootDir, env: process.env, encoding: "utf8" },
|
||||
);
|
||||
if (playwrightResult.error || playwrightResult.status !== 0) {
|
||||
process.stderr.write(playwrightResult.stderr ?? "");
|
||||
console.error("Unable to cache @playwright/mcp@0.0.78 for the managed MCP profile.");
|
||||
process.exit(playwrightResult.status ?? 1);
|
||||
if (platform() === "darwin") {
|
||||
// Browser automation is opt-in on macOS; do not install Playwright during setup.
|
||||
} else {
|
||||
const playwrightResult = spawnSync(
|
||||
process.execPath,
|
||||
[npmCliPath, "exec", "--yes", "--package=@playwright/mcp@0.0.78", "--", "playwright-mcp", "--version"],
|
||||
{ cwd: rootDir, env: process.env, encoding: "utf8" },
|
||||
);
|
||||
if (playwrightResult.error || playwrightResult.status !== 0) {
|
||||
process.stderr.write(playwrightResult.stderr ?? "");
|
||||
console.error("Unable to cache @playwright/mcp@0.0.78 for the managed MCP profile.");
|
||||
process.exit(playwrightResult.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (platform() === "win32") {
|
||||
|
||||
+3
-1
@@ -44,9 +44,11 @@ const managedDirectories = [
|
||||
join(resourcesDir, "themes"),
|
||||
];
|
||||
|
||||
const mcpDefaultsFile = platform() === "darwin" ? "mcp.macos.default.json" : "mcp.default.json";
|
||||
|
||||
const seedFiles = [
|
||||
[join(configDir, "settings.default.json"), join(agentDir, "settings.json")],
|
||||
[join(configDir, "mcp.default.json"), join(agentDir, "mcp.json")],
|
||||
[join(configDir, mcpDefaultsFile), join(agentDir, "mcp.json")],
|
||||
[join(configDir, "models.example.json"), join(agentDir, "models.json")],
|
||||
[join(configDir, "subagents.default.json"), join(agentDir, "extensions", "subagent", "config.json")],
|
||||
[join(configDir, "subagent-tool-description.default.md"), join(agentDir, "subagent-tool-description.md")],
|
||||
|
||||
+76
-4
@@ -1,5 +1,6 @@
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { platform } from "node:os";
|
||||
import { isLocalPortListening } from "./storage-maintenance.mjs";
|
||||
import { rootDir } from "./profile.mjs";
|
||||
|
||||
const DEV_PORT = 30141;
|
||||
@@ -49,12 +50,83 @@ exit 0
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
}
|
||||
|
||||
if (platform() !== "win32") {
|
||||
console.error("npm run restart currently supports Windows only. Stop the existing dev process and run npm run dev.");
|
||||
process.exit(1);
|
||||
function readMacListenerPids() {
|
||||
const result = spawnSync(
|
||||
"lsof",
|
||||
["-nP", "-t", `-iTCP:${DEV_PORT}`, "-sTCP:LISTEN"],
|
||||
{ cwd: rootDir, encoding: "utf8" },
|
||||
);
|
||||
if (result.error) throw result.error;
|
||||
const output = result.stdout ?? "";
|
||||
if (result.status !== 0 && !output.trim()) return [];
|
||||
|
||||
return [...new Set(
|
||||
output
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.map((value) => Number.parseInt(value, 10))
|
||||
.filter((pid) => Number.isInteger(pid) && pid > 0),
|
||||
)];
|
||||
}
|
||||
|
||||
stopWindowsDevelopmentProcesses();
|
||||
function readMacProcessCommand(pid) {
|
||||
const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
return result.status === 0 ? (result.stdout ?? "").trim() : "";
|
||||
}
|
||||
|
||||
function signalProcess(pid, signal) {
|
||||
try {
|
||||
process.kill(pid, signal);
|
||||
} catch (error) {
|
||||
if (error?.code !== "ESRCH") throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForPortClosed(timeoutMs) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (await isLocalPortListening(DEV_PORT)) {
|
||||
if (Date.now() >= deadline) return false;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function stopMacDevelopmentProcesses() {
|
||||
const listenerPids = readMacListenerPids();
|
||||
if (listenerPids.length === 0) return;
|
||||
|
||||
const projectPids = listenerPids.filter((pid) => readMacProcessCommand(pid).includes(rootDir));
|
||||
const foreignPids = listenerPids.filter((pid) => !projectPids.includes(pid));
|
||||
if (foreignPids.length > 0 || projectPids.length === 0) {
|
||||
const pids = foreignPids.length > 0 ? foreignPids : listenerPids;
|
||||
throw new Error(`Port ${DEV_PORT} is occupied by a process that does not belong to this project (PID ${pids.join(", ")}).`);
|
||||
}
|
||||
|
||||
for (const pid of projectPids) signalProcess(pid, "SIGTERM");
|
||||
if (await waitForPortClosed(3000)) return;
|
||||
|
||||
for (const pid of projectPids) signalProcess(pid, "SIGKILL");
|
||||
if (!(await waitForPortClosed(1000))) {
|
||||
throw new Error(`Port ${DEV_PORT} is still occupied by this project after termination (PID ${projectPids.join(", ")}).`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (platform() === "win32") {
|
||||
stopWindowsDevelopmentProcesses();
|
||||
} else if (platform() === "darwin") {
|
||||
await stopMacDevelopmentProcesses();
|
||||
} else {
|
||||
throw new Error("npm run restart currently supports Windows and macOS only. Stop the existing dev process and run npm run dev.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const npmCliPath = process.env.npm_execpath;
|
||||
if (!npmCliPath) {
|
||||
|
||||
Reference in New Issue
Block a user