mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 11:09:34 +00:00
feat: integrate Pi backend and Pi Web
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
|
||||
const MIN_NODE_VERSION = "22.19.0";
|
||||
|
||||
function parseNodeVersion(version) {
|
||||
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(version);
|
||||
if (!match) return null;
|
||||
return match.slice(1).map(Number);
|
||||
}
|
||||
|
||||
function isNodeVersionSupported(version) {
|
||||
const current = parseNodeVersion(version);
|
||||
const minimum = parseNodeVersion(MIN_NODE_VERSION);
|
||||
if (!current || !minimum) return false;
|
||||
|
||||
for (let index = 0; index < minimum.length; index += 1) {
|
||||
if (current[index] > minimum[index]) return true;
|
||||
if (current[index] < minimum[index]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getUnsupportedNodeVersionMessage(version) {
|
||||
return [
|
||||
`Pi Web requires Node.js ${MIN_NODE_VERSION} or newer.`,
|
||||
`Current Node.js version: ${version}.`,
|
||||
"Upgrade Node.js and try again: https://nodejs.org/",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MIN_NODE_VERSION,
|
||||
getUnsupportedNodeVersionMessage,
|
||||
isNodeVersionSupported,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { parseArgs } = require("util");
|
||||
|
||||
const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
|
||||
|
||||
function isEnabled(value) {
|
||||
return typeof value === "string" && TRUE_VALUES.has(value.trim().toLowerCase());
|
||||
}
|
||||
|
||||
function parseLaunchOptions(args = process.argv.slice(2), env = process.env) {
|
||||
const { values: cliArgs } = parseArgs({
|
||||
args,
|
||||
options: {
|
||||
port: { type: "string", short: "p" },
|
||||
hostname: { type: "string", short: "H" },
|
||||
"no-open": { type: "boolean" },
|
||||
},
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return {
|
||||
port: cliArgs.port ?? env.PORT ?? "30141",
|
||||
hostname: cliArgs.hostname ?? env.PI_WEB_HOSTNAME ?? "127.0.0.1",
|
||||
openBrowser: !cliArgs["no-open"] && !isEnabled(env.PI_WEB_NO_OPEN),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { parseLaunchOptions };
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { getUnsupportedNodeVersionMessage, isNodeVersionSupported } = require("./node-version");
|
||||
|
||||
if (!isNodeVersionSupported(process.versions.node)) {
|
||||
console.error(getUnsupportedNodeVersionMessage(process.versions.node));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { spawn } = require("child_process");
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const path = require("path");
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const fs = require("fs");
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { parseLaunchOptions } = require("./pi-web-options");
|
||||
|
||||
const pkgDir = path.join(__dirname, "..");
|
||||
const nextDir = path.join(pkgDir, ".next");
|
||||
|
||||
// Resolve next's CLI entry directly to avoid relying on .bin symlinks (which
|
||||
// may not exist when installed via npx).
|
||||
let nextBin;
|
||||
try {
|
||||
nextBin = require.resolve("next/dist/bin/next", { paths: [pkgDir] });
|
||||
} catch {
|
||||
// Fallback: locate next package root and derive the bin path manually.
|
||||
try {
|
||||
const nextPkg = require.resolve("next/package.json", { paths: [pkgDir] });
|
||||
nextBin = path.join(path.dirname(nextPkg), "dist", "bin", "next");
|
||||
} catch {
|
||||
nextBin = path.join(pkgDir, "node_modules", "next", "dist", "bin", "next");
|
||||
}
|
||||
}
|
||||
|
||||
const { port, hostname, openBrowser } = parseLaunchOptions();
|
||||
const loopbackHostnames = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
||||
|
||||
if (!fs.existsSync(nextDir)) {
|
||||
console.error("Build artifacts not found. Please report this issue.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!loopbackHostnames.has(hostname)) {
|
||||
console.warn(
|
||||
`Warning: pi-web is listening on ${hostname} without authentication. Only use this on a trusted network.`,
|
||||
);
|
||||
}
|
||||
|
||||
const nextArgs = ["start", "-p", port];
|
||||
nextArgs.push("-H", hostname);
|
||||
|
||||
// Always run next's JS entry with node directly — avoids .bin symlink issues
|
||||
// and path-with-spaces problems on Windows when shell: true is used.
|
||||
const child = spawn(process.execPath, [nextBin, ...nextArgs], {
|
||||
cwd: pkgDir,
|
||||
stdio: ["inherit", "pipe", "inherit"],
|
||||
env: { ...process.env, PI_WEB_HOSTNAME: hostname },
|
||||
});
|
||||
|
||||
let browserOpened = false;
|
||||
const url = `http://${hostname}:${port}`;
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
const text = chunk.toString();
|
||||
process.stdout.write(text);
|
||||
if (openBrowser && !browserOpened && text.includes("Ready")) {
|
||||
browserOpened = true;
|
||||
const isWindows = process.platform === "win32";
|
||||
const isMac = process.platform === "darwin";
|
||||
const openCmd = isWindows ? "start" : isMac ? "open" : "xdg-open";
|
||||
const opener = spawn(openCmd, [url], {
|
||||
shell: isWindows,
|
||||
stdio: "ignore",
|
||||
detached: true,
|
||||
});
|
||||
|
||||
opener.on("error", (error) => {
|
||||
console.warn(`Could not open browser automatically: ${error.message}`);
|
||||
});
|
||||
|
||||
opener.unref();
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code) => process.exit(code ?? 0));
|
||||
Reference in New Issue
Block a user