Files
openidea/src/features/remote-dev/environment.ts
T
l0ng-ai 1f9be73174 feat(remote): Reload the environment and apply devcontainer settings on the remote server (#183)
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
2026-10-29 09:28:18 +08:00

65 lines
2.1 KiB
TypeScript

/**
* Reload Environment: new terminals, runs, language servers and tools get the login
* shell's current environment (and, on a remote host, the project's `devcontainer.json`
* `remoteEnv`) without restarting the backend. What is already running keeps its own.
*/
import { tx } from "@/platform/i18n";
import { call, commands, type EnvReloaded } from "@/platform/ipc";
import { notifications } from "@/workbench/contributions";
const KEY = "remote.reloadEnvironment";
/** Variable names listed in the notification before "and N more". */
const LISTED = 8;
/** The notification text for a reload result. */
export function reloadSummary(r: EnvReloaded): string {
const names = r.changed.slice(0, LISTED).join(", ");
const more = r.changed.length - LISTED;
const changed =
r.changed.length === 0
? tx("No variables changed.")
: more > 0
? tx`Changed: ${names} and ${more} more.`
: tx`Changed: ${names}.`;
const keep = tx("Terminals and processes already running keep their old environment; new ones get the new one.");
return [changed, keep].join(" ");
}
async function restartLanguageServers() {
try {
const servers = await call(commands.lspServers());
await Promise.all(servers.map((s) => call(commands.lspRestart(s.id))));
} catch (e) {
notifications().notify({
group: "lsp",
level: "error",
title: "Failed to restart language server",
body: e instanceof Error ? e.message : String(e),
});
}
}
export async function reloadEnvironment() {
let result: EnvReloaded;
try {
result = await call(commands.envReload());
} catch (e) {
notifications().notify({
key: KEY,
level: "error",
title: "Could not reload the environment",
body: e instanceof Error ? e.message : String(e),
});
return;
}
notifications().notify({
key: KEY,
level: "info",
title: "Environment reloaded",
body: reloadSummary(result),
actions: result.changed.length
? [{ label: "Restart Language Servers", run: () => void restartLanguageServers() }]
: [],
});
}