дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
The remote server read the login shell's PATH once and cached it, had no project-level environment, and changed environment variables only took effect after reinstalling or a manual restart.
- Every openidea-server start re-reads the remote login shell's full environment (`$SHELL -ilc env -0`), with the cached PATH only as a fallback when the shell doesn't answer.
- A project's `.devcontainer/devcontainer.json` (or `.devcontainer.json`) applies on remote hosts: `remoteEnv` (with `${localEnv|containerEnv|env:X[:default]}`, workspace variables and `null` to unset) reaches every child process through one environment overlay (terminals, run configurations, language servers, debuggers, build tools, git), and `postStartCommand` (string, array or parallel object) runs once per server start in the project root as a cancellable background task; a failure raises a notification with the exit code and the last output lines without blocking the project.
- Reload Environment (`remote.reloadEnvironment`, File menu and Find Action) re-resolves the login shell and `remoteEnv` without restarting the server, starting from the process's original environment so prepended PATH entries don't pile up; it lists the changed variables, notes that running processes keep their environment, and offers to restart language servers.
- The JSONC reader moves to `ide_core::jsonc` and is shared with ide-run.
Claude-Session: https://claude.ai/code/session_01Dq6jQQn8ZhRfkkyj3PRutA
67 рядки
3.0 KiB
TypeScript
67 рядки
3.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { EnvReloaded, IdeError } from "@/bindings";
|
|
import type { NotificationService } from "@/workbench/contributions";
|
|
|
|
type Outcome = { status: "ok"; data: EnvReloaded } | { status: "error"; error: IdeError };
|
|
let outcome: Outcome;
|
|
const envReload = vi.fn(async () => outcome);
|
|
const lspServers = vi.fn(async () => ({ status: "ok" as const, data: [{ id: 1 }, { id: 2 }] }));
|
|
const lspRestart = vi.fn(async (_id: number) => ({ status: "ok" as const, data: null }));
|
|
vi.mock("@/platform/ipc", async (orig) => {
|
|
const actual = await orig<typeof import("@/platform/ipc")>();
|
|
return { ...actual, commands: { ...actual.commands, envReload, lspServers, lspRestart } };
|
|
});
|
|
|
|
const { reloadEnvironment, reloadSummary } = await import("./environment");
|
|
const { provideNotificationService } = await import("@/workbench/contributions");
|
|
|
|
type Note = Parameters<NotificationService["notify"]>[0];
|
|
let notes: Note[];
|
|
|
|
beforeEach(() => {
|
|
notes = [];
|
|
provideNotificationService({ notify: (n) => void notes.push(n) });
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const reloaded = (changed: string[]): EnvReloaded => ({ changed, devcontainer: null, shellRead: true });
|
|
|
|
describe("Reload Environment", () => {
|
|
it("says what changed and that running processes keep their environment", () => {
|
|
expect(reloadSummary(reloaded([]))).toBe(
|
|
"No variables changed. Terminals and processes already running keep their old environment; new ones get the new one.",
|
|
);
|
|
expect(reloadSummary(reloaded(["JAVA_HOME", "PATH"]))).toMatch(/^Changed: JAVA_HOME, PATH\. Terminals/);
|
|
const many = Array.from({ length: 10 }, (_, i) => `V${i}`);
|
|
expect(reloadSummary(reloaded(many))).toMatch(/^Changed: V0, V1, V2, V3, V4, V5, V6, V7 and 2 more\./);
|
|
});
|
|
|
|
it("offers to restart the language servers when something changed", async () => {
|
|
outcome = { status: "ok", data: reloaded(["PATH"]) };
|
|
await reloadEnvironment();
|
|
expect(envReload).toHaveBeenCalledOnce();
|
|
expect(notes).toHaveLength(1);
|
|
expect(notes[0]).toMatchObject({ level: "info", title: "Environment reloaded" });
|
|
const restart = notes[0]!.actions!.find((a) => a.label === "Restart Language Servers")!;
|
|
restart.run();
|
|
await vi.waitFor(() => expect(lspRestart).toHaveBeenCalledTimes(2));
|
|
expect(lspRestart.mock.calls.map((c) => c[0])).toEqual([1, 2]);
|
|
});
|
|
|
|
it("has nothing to restart when nothing changed", async () => {
|
|
outcome = { status: "ok", data: reloaded([]) };
|
|
await reloadEnvironment();
|
|
expect(notes[0]!.actions).toEqual([]);
|
|
});
|
|
|
|
it("reports a failure as an error", async () => {
|
|
outcome = {
|
|
status: "error",
|
|
error: { kind: "unavailable", message: "could not read the environment of the login shell (/bin/zsh)" },
|
|
} as Outcome;
|
|
await reloadEnvironment();
|
|
expect(notes[0]).toMatchObject({ level: "error", title: "Could not reload the environment" });
|
|
expect(String(notes[0]!.body)).toContain("login shell");
|
|
});
|
|
});
|