mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 18:22:38 +00:00
fix(migrate): validate live source state
This commit is contained in:
@@ -34,6 +34,7 @@ export function useMigrationAvailability(source: string) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
let retry: ReturnType<typeof setTimeout> | null = null;
|
||||
async function loadAvailability() {
|
||||
const next = await getDesktopHost()?.migrations?.getAvailability?.({ source });
|
||||
if (active && next) {
|
||||
@@ -43,11 +44,15 @@ export function useMigrationAvailability(source: string) {
|
||||
? t(`desktop.integrations.migration.availability.${next.reason}`)
|
||||
: null,
|
||||
});
|
||||
if (next.reason === "host-not-running") {
|
||||
retry = setTimeout(() => void loadAvailability(), 2_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
void loadAvailability();
|
||||
return () => {
|
||||
active = false;
|
||||
if (retry) clearTimeout(retry);
|
||||
};
|
||||
}, [source, t]);
|
||||
return {
|
||||
|
||||
@@ -19,7 +19,10 @@ test("discovers the configured local socket before TCP fallback", () => {
|
||||
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "paseo-node-client-"));
|
||||
cleanup.push(paseoHome);
|
||||
mkdirSync(paseoHome, { recursive: true });
|
||||
writeFileSync(path.join(paseoHome, "paseo.pid"), JSON.stringify({ listen: "/tmp/paseo.sock" }));
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "paseo.pid"),
|
||||
JSON.stringify({ pid: process.pid, listen: "/tmp/paseo.sock" }),
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "config.json"),
|
||||
JSON.stringify({ daemon: { listen: "127.0.0.1:7777" } }),
|
||||
@@ -35,7 +38,10 @@ test("discovers the configured local socket before TCP fallback", () => {
|
||||
test("discovers the running daemon TCP address from its PID record", () => {
|
||||
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "paseo-node-client-pid-tcp-"));
|
||||
cleanup.push(paseoHome);
|
||||
writeFileSync(path.join(paseoHome, "paseo.pid"), JSON.stringify({ listen: "127.0.0.1:7789" }));
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "paseo.pid"),
|
||||
JSON.stringify({ pid: process.pid, listen: "127.0.0.1:7789" }),
|
||||
);
|
||||
|
||||
expect(resolveDefaultDaemonHosts({ PASEO_HOME: paseoHome })).toEqual([
|
||||
"127.0.0.1:7789",
|
||||
@@ -46,7 +52,10 @@ test("discovers the running daemon TCP address from its PID record", () => {
|
||||
test("normalizes the daemon's bare IPv6 loopback PID address", () => {
|
||||
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "paseo-node-client-pid-ipv6-"));
|
||||
cleanup.push(paseoHome);
|
||||
writeFileSync(path.join(paseoHome, "paseo.pid"), JSON.stringify({ listen: "::1:7789" }));
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "paseo.pid"),
|
||||
JSON.stringify({ pid: process.pid, listen: "::1:7789" }),
|
||||
);
|
||||
|
||||
expect(resolveDefaultDaemonHosts({ PASEO_HOME: paseoHome })).toEqual([
|
||||
"[::1]:7789",
|
||||
@@ -102,3 +111,21 @@ test("skips malformed discovered candidates and keeps the default fallback", ()
|
||||
resolveDefaultDaemonHosts({ PASEO_HOME: paseoHome, PASEO_LISTEN: "tcp://missing-port" }),
|
||||
).toEqual(["localhost:6767"]);
|
||||
});
|
||||
|
||||
test("ignores a stale PID listener before the configured daemon", () => {
|
||||
const paseoHome = mkdtempSync(path.join(os.tmpdir(), "paseo-node-client-stale-pid-"));
|
||||
cleanup.push(paseoHome);
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "paseo.pid"),
|
||||
JSON.stringify({ pid: 2_147_483_647, listen: "127.0.0.1:7789" }),
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(paseoHome, "config.json"),
|
||||
JSON.stringify({ daemon: { listen: "127.0.0.1:7777" } }),
|
||||
);
|
||||
|
||||
expect(resolveDefaultDaemonHosts({ PASEO_HOME: paseoHome })).toEqual([
|
||||
"127.0.0.1:7777",
|
||||
"localhost:6767",
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -111,9 +111,17 @@ function readPidListen(paseoHome: string): string | null {
|
||||
if (!existsSync(pidPath)) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(pidPath, "utf8")) as {
|
||||
pid?: unknown;
|
||||
listen?: unknown;
|
||||
sockPath?: unknown;
|
||||
};
|
||||
if (
|
||||
typeof parsed.pid !== "number" ||
|
||||
!Number.isInteger(parsed.pid) ||
|
||||
!isProcessRunning(parsed.pid)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (typeof parsed.listen === "string") return parsed.listen;
|
||||
return typeof parsed.sockPath === "string" ? parsed.sockPath : null;
|
||||
} catch {
|
||||
@@ -121,6 +129,15 @@ function readPidListen(paseoHome: string): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
function isProcessRunning(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return typeof error === "object" && error !== null && "code" in error && error.code === "EPERM";
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDefaultDaemonHosts(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
const paseoHome = resolvePaseoHome(env);
|
||||
const direct = normalizeDaemonHost(env.PASEO_LISTEN ?? "");
|
||||
|
||||
@@ -156,6 +156,7 @@ test("preserves literal or escaped Conductor names and rewrites active variables
|
||||
escaped: { command: "printf \\$CONDUCTOR_PORT" },
|
||||
escapedDouble: { command: 'printf "\\$CONDUCTOR_PORT"' },
|
||||
bare: { command: "printf CONDUCTOR_PORT" },
|
||||
heredoc: { command: "cat <<'EOF'\n$CONDUCTOR_PORT\nEOF" },
|
||||
active: { command: "printf \"'$CONDUCTOR_PORT'\"" },
|
||||
},
|
||||
},
|
||||
@@ -170,7 +171,11 @@ test("preserves literal or escaped Conductor names and rewrites active variables
|
||||
escapedDouble: { command: 'printf "\\$CONDUCTOR_PORT"' },
|
||||
},
|
||||
});
|
||||
expect(inspected.notices).toEqual([]);
|
||||
expect(inspected.notices).toContainEqual({
|
||||
code: "conductor-setting-unsupported",
|
||||
level: "warning",
|
||||
message: "scripts.heredoc: Here-document commands are not imported.",
|
||||
});
|
||||
});
|
||||
|
||||
test("skips cwd scripts on Windows instead of emitting POSIX shell syntax", () => {
|
||||
|
||||
@@ -369,6 +369,10 @@ function rewriteExactCommand(
|
||||
notices: MigrationNotice[],
|
||||
platform: NodeJS.Platform,
|
||||
): string | null {
|
||||
if (containsActiveHereDocument(command)) {
|
||||
notices.push(unsupportedSetting(key, "Here-document commands are not imported."));
|
||||
return null;
|
||||
}
|
||||
const sourceVariables = collectConductorVariables(command);
|
||||
if (platform === "win32" && sourceVariables.length > 0) {
|
||||
notices.push(
|
||||
@@ -714,6 +718,14 @@ function forEachActiveArithmetic(command: string, visit: (body: string) => void)
|
||||
}
|
||||
}
|
||||
|
||||
function containsActiveHereDocument(command: string): boolean {
|
||||
const mask = activeShellMask(command);
|
||||
for (const match of command.matchAll(/<<-?/g)) {
|
||||
if (mask.slice(match.index, match.index + match[0].length) === match[0]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function appendArgs(command: string, args: string[]): string {
|
||||
return args.length === 0 ? command : `${command} ${args.map(shellQuoteArgument).join(" ")}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user