Skip to content

Commit a00ef35

Browse files
committed
Implement initial API
1 parent 17bc028 commit a00ef35

File tree

3 files changed

+165
-9
lines changed

3 files changed

+165
-9
lines changed

README.md

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,86 @@ need to activate the Ruby environment in order to launch Ruby executables, such
77

88
## Features
99

10-
TODO: this extension is getting extracted from the Ruby LSP.
10+
- **Automatic Ruby detection**: Discovers the Ruby interpreter installed on your machine
11+
- **Version manager integrations**: Supports popular version managers including:
12+
- [chruby](https://github.com/postmodern/chruby)
13+
- [rbenv](https://github.com/rbenv/rbenv)
14+
- [rvm](https://rvm.io/)
15+
- [asdf](https://asdf-vm.com/) (with ruby plugin)
16+
- [mise](https://mise.jdx.dev/)
17+
- **Environment activation**: Composes the correct environment variables (`PATH`, `GEM_HOME`, `GEM_PATH`, etc.) to match your shell configuration
18+
- **JIT detection**: Identifies available JIT compilers (YJIT, ZJIT) for the activated Ruby version
19+
- **Shell support**: Works with various shells including bash, zsh, fish, and PowerShell
20+
- **Extension API**: Provides a programmatic API for other extensions to access the activated Ruby environment
1121

1222
## Extension Settings
1323

1424
TODO
1525

1626
## API
1727

18-
TODO
28+
This extension exposes an API that other extensions can use to access the activated Ruby environment.
29+
30+
### Getting the API
31+
32+
```typescript
33+
const rubyEnvExtension = vscode.extensions.getExtension("Shopify.ruby-environments");
34+
35+
if (rubyEnvExtension) {
36+
const api = rubyEnvExtension.exports;
37+
// Use the API...
38+
}
39+
```
40+
41+
### Activating the Ruby Environment
42+
43+
Request the extension to activate Ruby for a specific workspace:
44+
45+
```typescript
46+
api.activate(vscode.workspace.workspaceFolders?.[0]);
47+
```
48+
49+
### Getting the Current Ruby Definition
50+
51+
Retrieve the currently activated Ruby environment:
52+
53+
```typescript
54+
const ruby = api.getRuby();
55+
56+
if (ruby === null) {
57+
console.log("Ruby environment not yet activated");
58+
} else if (ruby.error) {
59+
console.log("Ruby activation failed");
60+
} else {
61+
console.log(`Ruby version: ${ruby.rubyVersion}`);
62+
console.log(`Available JITs: ${ruby.availableJITs.join(", ")}`);
63+
console.log(`GEM_PATH: ${ruby.gemPath.join(":")}`);
64+
}
65+
```
66+
67+
### Subscribing to Ruby Environment Changes
68+
69+
Listen for changes to the Ruby environment (e.g., when the user switches Ruby versions):
70+
71+
```typescript
72+
const disposable = api.onDidRubyChange((event) => {
73+
console.log(`Ruby changed in workspace: ${event.workspace?.name}`);
74+
75+
if (!event.ruby.error) {
76+
console.log(`New Ruby version: ${event.ruby.rubyVersion}`);
77+
}
78+
});
79+
80+
// Add to your extension's subscriptions for automatic cleanup
81+
context.subscriptions.push(disposable);
82+
```
83+
84+
### Extension Dependency
85+
86+
To ensure your extension loads after Ruby Environments, add it as a dependency in your `package.json`:
87+
88+
```json
89+
{
90+
"extensionDependencies": ["Shopify.ruby-environments"]
91+
}
92+
```

src/extension.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,68 @@
11
import * as vscode from "vscode";
22

3-
// The public API that gets exposed to other extensions that depend on Ruby environments
4-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
5-
interface RubyEnvironmentsApi {}
3+
/**
4+
* Represents a Ruby environment that failed to activate
5+
*/
6+
export interface RubyError {
7+
error: true;
8+
}
9+
10+
/**
11+
* JIT compiler types supported by Ruby
12+
*/
13+
export enum JitType {
14+
YJIT = "YJIT",
15+
ZJIT = "ZJIT",
16+
}
17+
18+
/**
19+
* Represents a successfully activated Ruby environment
20+
*/
21+
export interface RubyEnvironment {
22+
error: false;
23+
rubyVersion: string;
24+
availableJITs: JitType[];
25+
env: NodeJS.ProcessEnv;
26+
gemPath: string[];
27+
}
28+
29+
/**
30+
* Represents a Ruby environment definition - either an error or a successful environment
31+
*/
32+
export type RubyDefinition = RubyError | RubyEnvironment;
33+
34+
/**
35+
* Event data emitted when the Ruby environment changes
36+
*/
37+
export interface RubyChangeEvent {
38+
workspace: vscode.WorkspaceFolder | undefined;
39+
ruby: RubyDefinition;
40+
}
41+
42+
/**
43+
* The public API that gets exposed to other extensions that depend on Ruby environments
44+
*/
45+
export interface RubyEnvironmentsApi {
46+
/** Activate the extension for a specific workspace */
47+
activate: (workspace: vscode.WorkspaceFolder | undefined) => void;
48+
/** Get the current Ruby definition */
49+
getRuby: () => RubyDefinition | null;
50+
/** Event that fires when the Ruby environment changes */
51+
onDidRubyChange: vscode.Event<RubyChangeEvent>;
52+
}
53+
54+
// Event emitter for Ruby environment changes
55+
const rubyChangeEmitter = new vscode.EventEmitter<RubyChangeEvent>();
56+
57+
export function activate(context: vscode.ExtensionContext): RubyEnvironmentsApi {
58+
// Ensure the event emitter is disposed when the extension is deactivated
59+
context.subscriptions.push(rubyChangeEmitter);
660

7-
export function activate(_context: vscode.ExtensionContext): RubyEnvironmentsApi {
8-
return {};
61+
return {
62+
activate: (_workspace: vscode.WorkspaceFolder | undefined) => {},
63+
getRuby: () => null,
64+
onDidRubyChange: rubyChangeEmitter.event,
65+
};
966
}
1067

1168
export function deactivate() {}

src/test/extension.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,32 @@
11
import * as assert from "assert";
2+
import * as vscode from "vscode";
3+
import { activate, deactivate } from "../extension";
24

35
suite("Extension Test Suite", () => {
4-
test("Sample test", () => {
5-
assert.strictEqual(1 + 1, 2);
6+
suite("activate", () => {
7+
test("returns an API object with activate and getRuby methods", () => {
8+
const mockContext = {} as vscode.ExtensionContext;
9+
const api = activate(mockContext);
10+
11+
assert.ok(api, "API should be defined");
12+
assert.strictEqual(typeof api.activate, "function", "activate should be a function");
13+
assert.strictEqual(typeof api.getRuby, "function", "getRuby should be a function");
14+
});
15+
16+
test("multiple activate calls return independent API instances", () => {
17+
const mockContext = {} as vscode.ExtensionContext;
18+
const api1 = activate(mockContext);
19+
const api2 = activate(mockContext);
20+
21+
assert.notStrictEqual(api1, api2, "Each activate call should return a new API instance");
22+
});
23+
});
24+
25+
suite("deactivate", () => {
26+
test("can be called without errors", () => {
27+
assert.doesNotThrow(() => {
28+
deactivate();
29+
}, "deactivate should not throw errors");
30+
});
631
});
732
});

0 commit comments

Comments
 (0)