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
6 changes: 6 additions & 0 deletions clients/morphcloud/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1] - 2025-01-23

Release because initial release was broken.

Fixed Nuanced LSP dependency to v0.5.x.

## [0.1.0] - 2025-01-21

Initial release.
11 changes: 3 additions & 8 deletions clients/morphcloud/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,13 @@ npm run dev

This project uses Mutagen for file syncing. It uses an isolated configuration so it does not interfere with other uses of Mutagen on the system.

To run `mutagen` with the right configuration to see the file syncs created by this project, run:
The CLI has a hidden `mutagen` command that allows you to run Mutagen with the right configuration. For example:

```bash
scripts/mutagen.sh
npm run dev mutagen sync list
```

We use a [modified](https://github.com/nuanced-dev/mutagen/tree/hendrikvanantwerpen/custom-ssh-config) Mutagen version. The binaries are shipped as part of the NPM package.

If these need changing:

- Run `go run scripts/build.go --mode=release-slim` in a Mutagen checkout
- Run `scripts/updarte-mutagen-assets.sh /path/to/mutagen/checkout`
We use a [modified](https://github.com/nuanced-dev/mutagen/) Mutagen version which is published as [@nuanced-dev/mutagen](https://www.npmjs.com/package/@nuanced-dev/mutagen).

## Release

Expand Down
20 changes: 20 additions & 0 deletions clients/morphcloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ _Note that this client is experimental and not considered stable!_

**Known issues:**

- Mutagen easily gets into error states, where resuming syncs hangs or setting up syncs fails. Often the only way to recover is removing the mutagen data directory (`~/.nuanced/mutagen`).
- File syncs are sometimes not properly resumed, in which case the workspace instance needs to be recreated.
- Morph Cloud retains snapshots for instances that have been deleted. These snapshots do not inherit the metadata of the instance and we currently cannot clean them up.

Expand Down Expand Up @@ -69,6 +70,25 @@ nuanced-lsp-morphcloud workspace delete /path/to/workspace

This can be useful if you want to recreate the workspace instance.

**List workspaces:**

```bash
nuanced-lsp-morphcloud workspace list
```

## Troubleshooting

**Mutagen sync setup fails:**

Starting a workspace server fails with an error like the following:

```
Connecting to agent (POSIX)...
Error: unable to connect to beta: unable to connect to endpoint: unable to dial agent endpoint: unable to handshake with agent process: unable to receive server magic number: EOF (error output: No user exists for uid 504)
```

Mutagen got in a bad state. Remove `~/.nuanced/mutagen` to have syncs recreated.

## License

This work is licensed under the terms of the MIT license. For a copy, see [LICENSE](LICENSE) or <https://opensource.org/licenses/MIT>.
3 changes: 2 additions & 1 deletion clients/morphcloud/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nuanced-dev/lsp-morphcloud",
"version": "0.1.0",
"version": "0.1.1",
"description": "CLI to run Nuanced LSP on Morphcloud",
"license": "MIT",
"author": "Nuanced",
Expand All @@ -24,6 +24,7 @@
"lint:fix": "eslint --fix"
},
"dependencies": {
"@nuanced-dev/mutagen": "^0.19.0-dev.1",
"child-process-promise": "^2.2.1",
"commander": "^14.0.2",
"node-machine-id": "^1.1.12",
Expand Down
32 changes: 0 additions & 32 deletions clients/morphcloud/scripts/mutagen.sh

This file was deleted.

47 changes: 0 additions & 47 deletions clients/morphcloud/scripts/update-mutagen-assets.sh

This file was deleted.

23 changes: 20 additions & 3 deletions clients/morphcloud/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,16 @@ workspace
.description("Run LSP server for a workspace")
.argument("<directory>", "Workspace directory")
.action(async (directory: string) => {
const { workspaceServer: workspaceLsp } =
await import("./commands/workspace-server.js");
workspaceLsp(directory).catch(console.error);
const { workspaceServer } = await import("./commands/workspace-server.js");
workspaceServer(directory).catch(console.error);
});

workspace
.command("list")
.description("Show workspace instances")
.action(async () => {
const { workspaceList } = await import("./commands/workspace-list.js");
workspaceList().catch(console.error);
});

workspace
Expand All @@ -69,4 +76,14 @@ workspace
workspaceStop(directory).catch(console.error);
});

program
.command("mutagen", { hidden: true })
.description("Run mutagen command")
.allowUnknownOption()
.argument("[args...]", "Mutagen arguments")
.action(async (args: string[]) => {
const { mutagenCommand } = await import("./commands/mutagen.js");
mutagenCommand(args).catch(console.error);
});

program.parse();
13 changes: 13 additions & 0 deletions clients/morphcloud/src/commands/mutagen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MutagenError } from "@nuanced-dev/mutagen";
import { spawnMutagen } from "../util/mutagen.js";

export async function mutagenCommand(args: string[]) {
try {
const o = await spawnMutagen(args);
console.log(o.stdout);
} catch (e) {
const me = e as MutagenError;
console.error(me.stderr);
process.exitCode = me.exitCode;
}
}
13 changes: 9 additions & 4 deletions clients/morphcloud/src/commands/service-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Instance, MorphCloudClient } from "morphcloud";
import {
LABEL_NUANCED_LSP_ROLE,
NUANCED_LSP_ROLE_SERVICE,
NUANCED_LSP_VERSION,
} from "../util/constants.js";
import {
execOrThrow,
Expand Down Expand Up @@ -76,10 +77,14 @@ export async function serviceCreate() {
console.log(` Installed ${dockerVersion.trim()}`);

console.log("- Installing Nuanced LSP...");
await execOrThrow(serviceInstance, "npm install -g @nuanced-dev/lsp", {
verbose: VERBOSE,
prefix: " | ",
});
await execOrThrow(
serviceInstance,
`npm install -g @nuanced-dev/lsp${NUANCED_LSP_VERSION}`,
{
verbose: VERBOSE,
prefix: " | ",
},
);
const nuancedVersion = await execOrThrow(
serviceInstance,
"nuanced-lsp --version",
Expand Down
25 changes: 2 additions & 23 deletions clients/morphcloud/src/commands/service-status.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,19 @@
import { MorphCloudClient } from "morphcloud";
import {
LABEL_NUANCED_LSP_ROLE,
LABEL_NUANCED_LSP_WORKSPACE_PATH,
LABEL_NUANCED_LSP_WORKSPACE_HOSTNAME,
NUANCED_LSP_ROLE_SERVICE,
NUANCED_LSP_ROLE_WORKSPACE,
} from "../util/constants.js";
import {
findSnapshot,
formatInstanceStatus,
listInstances,
} from "../util/morphcloud.js";
import { findSnapshot } from "../util/morphcloud.js";

export async function serviceStatus() {
const client = new MorphCloudClient();
try {
console.log("Service snapshots:");
const serviceSnapshot = await findSnapshot(client, {
metadata: { [LABEL_NUANCED_LSP_ROLE]: NUANCED_LSP_ROLE_SERVICE },
});
console.log(
`- service${" ".repeat(13)} : ${serviceSnapshot ? serviceSnapshot.id : "<missing>"}`,
`Service snapshot: ${serviceSnapshot ? serviceSnapshot.id : "<missing>"}`,
);
console.log();
console.log("Workspace instances:");
for (const instance of await listInstances(client, {
metadata: { [LABEL_NUANCED_LSP_ROLE]: NUANCED_LSP_ROLE_WORKSPACE },
})) {
const hostname =
instance.metadata![LABEL_NUANCED_LSP_WORKSPACE_HOSTNAME] ?? "<unknown>";
const path = instance.metadata![LABEL_NUANCED_LSP_WORKSPACE_PATH]!;
const label = `${hostname}:${path}`;
console.log(
`- ${label.padEnd(40)} : ${instance.id} (${formatInstanceStatus(instance.status)})`,
);
}
} catch (error) {
throw new Error(`Service status failed: ${error}`);
}
Expand Down
28 changes: 28 additions & 0 deletions clients/morphcloud/src/commands/workspace-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MorphCloudClient } from "morphcloud";
import {
LABEL_NUANCED_LSP_ROLE,
LABEL_NUANCED_LSP_WORKSPACE_PATH,
LABEL_NUANCED_LSP_WORKSPACE_HOSTNAME,
NUANCED_LSP_ROLE_WORKSPACE,
} from "../util/constants.js";
import { formatInstanceStatus, listInstances } from "../util/morphcloud.js";

export async function workspaceList() {
const client = new MorphCloudClient();
try {
console.log("Workspace instances:");
for (const instance of await listInstances(client, {
metadata: { [LABEL_NUANCED_LSP_ROLE]: NUANCED_LSP_ROLE_WORKSPACE },
})) {
const hostname =
instance.metadata![LABEL_NUANCED_LSP_WORKSPACE_HOSTNAME] ?? "<unknown>";
const path = instance.metadata![LABEL_NUANCED_LSP_WORKSPACE_PATH]!;
const label = `${hostname}:${path}`;
console.log(
`- ${label.padEnd(40)} : ${instance.id} (${formatInstanceStatus(instance.status)})`,
);
}
} catch (error) {
throw new Error(`Service status failed: ${error}`);
}
}
Loading
Loading