Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub fn run() {
diagnostics::system_info,
service::server_service_status,
service::server_service_install,
service::server_link_path,
diagnostics::resolve_binaries,
diagnostics::logs_folder_open,
diagnostics::diagnostics_save,
Expand Down
20 changes: 20 additions & 0 deletions apps/desktop/src-tauri/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,26 @@ pub async fn server_service_install() -> Result<ServiceStatus, String> {
Ok(status)
}

/// Point the `rustcode` a shell runs at this app's engine (issue #3411): the
/// CLI's own `server link-path`, run by the bundled engine so the shell then
/// finds the app's version. Homebrew's copy and a development build are refused
/// there with the reason, which is shown as it is. Answers the state after it.
#[tauri::command]
pub async fn server_link_path() -> Result<ServiceStatus, String> {
let output = run(&["server", "link-path"], STATUS_TIMEOUT).await?;
if !output.status.success() {
let said = String::from_utf8_lossy(&output.stdout).trim().to_owned();
let why = String::from_utf8_lossy(&output.stderr).trim().to_owned();
let message = if why.is_empty() { said } else { why };
return Err(if message.is_empty() {
"`rustcode` on PATH could not be pointed at this app.".to_owned()
} else {
message
});
}
read_status().await
}

/// The current state from `rustcode server status --json`. A status that
/// cannot be read is a status the page says it does not know, never an error
/// the page cannot show.
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/acp/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5759,6 +5759,7 @@ export class MockTransport implements Transport {
this.serverInstalled = true;
return mockServerService(true);
}
if (command === "server_link_path") return mockServerService(this.serverInstalled);
if (command === "machines_set_main") {
this.machineList = this.machineList.map((machine) => ({
...machine,
Expand Down
32 changes: 31 additions & 1 deletion apps/desktop/src/state/machines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
gatewayLine,
installServer,
lastSeen,
linkCliToApp,
localAppVersion,
type Machine,
machineLine,
Expand Down Expand Up @@ -86,6 +87,8 @@ class FakeShell implements Transport {
}[] = [];
/** What the local search says when it failed; the overlay rows are still listed. */
discoverNotice: string | null = null;
/** Whether a terminal's `rustcode` reads as an older version. */
pathStale = false;
/** Whether the background server reads as installed. */
serverInstalled = true;
/** What `server_service_install` refuses with, when it does. */
Expand Down Expand Up @@ -131,6 +134,10 @@ class FakeShell implements Transport {
if (this.installError) throw this.installError;
this.serverInstalled = this.installAnswersInstalled;
return this.serverService();
case "server_link_path":
if (this.installError) throw this.installError;
this.pathStale = false;
return this.serverService();
case "app_version":
return this.appVersion;
case "machines_remove":
Expand All @@ -152,7 +159,14 @@ class FakeShell implements Transport {
running: installed,
version: installed ? "1.28.0" : null,
cliVersion: "1.28.0",
problems: [],
problems: this.pathStale
? [
{
code: "path-version-mismatch",
message: "`rustcode` on PATH is 1.25.0, but this engine is 1.28.0.",
},
]
: [],
// The shell that installs and still reads "not installed" keeps the
// command's own words here.
error:
Expand Down Expand Up @@ -196,6 +210,7 @@ beforeEach(() => {
pairingError: null,
server: null,
serverInstalling: false,
serverLinking: false,
});
forgetLocalAppVersion();
});
Expand Down Expand Up @@ -628,6 +643,21 @@ describe("the RustCode server in the background", () => {
expect(updateAdvice("macos")).toContain("brew upgrade rustcode");
});

test("pointing a terminal's rustcode at the app clears the version warning", async () => {
shell.pathStale = true;
await refreshServerService();
expect(useMachines.getState().server?.problems.map((problem) => problem.code)).toEqual([
"path-version-mismatch",
]);
expect(await linkCliToApp()).toBe(true);
expect(useMachines.getState().server?.problems).toEqual([]);
expect(useMachines.getState().serverLinking).toBe(false);
// A copy the CLI refuses to touch (Homebrew's) keeps its reason on screen.
shell.installError = new Error("Homebrew's copy; run `brew upgrade lhagfoss/tap/rustcode`");
expect(await linkCliToApp()).toBe(false);
expect(useMachines.getState().serverLinking).toBe(false);
});

test("an install that fails says why, and leaves the page without a server", async () => {
shell.serverInstalled = false;
shell.installError = new Error("a development build; run `rustcode server install` from a release");
Expand Down
32 changes: 32 additions & 0 deletions apps/desktop/src/state/machines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ interface MachinesStore {
server: ServerService | null;
/** An install is running now. */
serverInstalling: boolean;
/** Pointing the `rustcode` on PATH at this app's engine is running now. */
serverLinking: boolean;
}

export const useMachines = create<MachinesStore>(() => ({
Expand All @@ -216,6 +218,7 @@ export const useMachines = create<MachinesStore>(() => ({
pairingError: null,
server: null,
serverInstalling: false,
serverLinking: false,
}));

// ---------------------------------------------------------------- labels
Expand Down Expand Up @@ -678,6 +681,7 @@ export function resetMachinesForTests(): void {
pairingError: null,
server: null,
serverInstalling: false,
serverLinking: false,
});
}

Expand Down Expand Up @@ -837,6 +841,34 @@ export async function installServer(): Promise<boolean> {
}
}

/**
* Point the `rustcode` a shell runs at this app's engine (issue 3411): the
* CLI's own `rustcode server link-path`, run by the app's engine so it is the
* app's version that the shell then finds. Homebrew's copy is refused with the
* `brew` command. Answers whether the page now reads no version mismatch.
*/
export async function linkCliToApp(): Promise<boolean> {
useMachines.setState({ serverLinking: true });
try {
const server = await shellCall<ServerService>("server_link_path");
useMachines.setState({ server, serverLinking: false });
const stale = server.problems.some((problem) => problem.code.startsWith("path-version-mismatch"));
if (stale) {
toast("The `rustcode` on PATH still is not this app's version. See the warning on this page.", "error");
} else {
toast(
"`rustcode` in a terminal now runs this app's version. Open a new terminal to use it.",
"success",
);
}
return !stale;
} catch (error) {
useMachines.setState({ serverLinking: false });
toast(`Could not point \`rustcode\` at this app: ${errorText(error)}`, "error");
return false;
}
}

/**
* Ask before installing the server, in the words that say what it does: it is
* where threads can live instead of the app, it stays up when the app closes,
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/ui/MachinesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
discoverMachines,
gatewayLine,
lastSeen,
linkCliToApp,
localAppVersion,
type Machine,
machineLine,
Expand Down Expand Up @@ -143,6 +144,7 @@ function ThisComputer() {
const open = useApp((s) => Object.values(s.threads).filter((thread) => thread.loaded).length);
const server = useMachines((s) => s.server);
const installing = useMachines((s) => s.serverInstalling);
const linking = useMachines((s) => s.serverLinking);
const platform = currentPlatform();
const running = status === "ready";
const installed = server?.installed === true;
Expand Down Expand Up @@ -190,6 +192,14 @@ function ThisComputer() {
Install server
</Action>
)}
{/* One action for a terminal's older `rustcode`: the install script's copy
or any other that Homebrew does not own (issue 3411). */}
{server?.problems.some((problem) => problem.code === "path-version-mismatch") && (
<Action disabled={linking} onClick={() => void linkCliToApp()}>
{linking ? <Spinner /> : <SquareTerminal size={13} />}
Use this app's version in a terminal
</Action>
)}
</Row>
);
}
Expand Down
13 changes: 13 additions & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ touches.
| `path-version-mismatch-server` | The same copy is another version than the running server, which is a different build; the message names the server's binary and version. |
| `unmanaged` | The server that answers was not started by the service (an app started it); `restart` hands it over. |

### `link-path`

`rustcode server link-path` is the one action behind that warning (Settings ›
Machines › "Use this app's version in a terminal"). It points the `rustcode` a
shell finds at the engine that runs it: on macOS and Linux the copy becomes a
symlink to this engine, so the next app update reaches it too; Windows has no
unprivileged symlink and gets a copy (a running `rustcode.exe` is renamed
aside first). It replaces the file in place and never touches `PATH`. It does
nothing when a shell already finds this version, leaves Homebrew's copy to
`brew upgrade lhagfoss/tap/rustcode`, and refuses a development build. Run by
the app, it is the app's engine, so it is the app's version that a terminal
runs afterwards.

### `restart`

`restart` stops the server and starts it from the installed definition, then
Expand Down
Loading
Loading