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
119 changes: 119 additions & 0 deletions .github/workflows/cross-compile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# inspired by https://github.com/topheman/update-homebrew-tap-playground/blob/master/.github/workflows/cross-compile.yml
name: Cross-Compile

on: [push]

env:
CARGO_TERM_COLOR: always
BINARY_NAME: pluginlab

jobs:
build:
if: github.ref_type == 'tag'
name: Cross-Compile ${{ matrix.platform.target }}
strategy:
matrix:
platform:

- runs-on: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- runs-on: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- runs-on: macos-14
target: x86_64-apple-darwin
- runs-on: macos-14
target: aarch64-apple-darwin
runs-on: ${{ matrix.platform.runs-on }}
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Build binary
uses: houseabsolute/actions-rust-cross@v1
with:
command: build
target: ${{ matrix.platform.target }}
args: "--locked --release -p pluginlab"
strip: true
- name: Generate completions
run: |
mkdir -p ./tmp/${{ matrix.platform.target }}
cp target/${{ matrix.platform.target }}/release/${{ env.BINARY_NAME }} ./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }}
mkdir -p ./tmp/${{ matrix.platform.target }}/completions/{zsh,bash,fish}
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell zsh > ./tmp/${{ matrix.platform.target }}/completions/zsh/_${{ env.BINARY_NAME }}
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell bash > ./tmp/${{ matrix.platform.target }}/completions/bash/${{ env.BINARY_NAME }}
./tmp/${{ matrix.platform.target }}/${{ env.BINARY_NAME }} generate-completions --shell fish > ./tmp/${{ matrix.platform.target }}/completions/fish/${{ env.BINARY_NAME }}.fish
- name: Compress
run: |
mkdir -p ./compressed
cd ./tmp/${{ matrix.platform.target }}
tar -cvf ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz ${{ env.BINARY_NAME }} completions
mv ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz ../../compressed
- name: Cache compressed binaries
uses: actions/cache@v4
with:
path: ./compressed
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
upload-archives:
if: false # This step is totally skipped, activate the workflow outside of a tag (for debug purposes)
needs: build
name: Upload archive for build ${{ matrix.platform.target }}
strategy:
matrix:
platform:
- target: x86_64-unknown-linux-gnu
- target: aarch64-unknown-linux-gnu
- target: x86_64-apple-darwin
- target: aarch64-apple-darwin
runs-on: ubuntu-latest
steps:
- name: Restore cached compressed binaries
uses: actions/cache/restore@v4
with:
path: ./compressed
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
- name: Upload archive
uses: actions/upload-artifact@v4
with:
path: ./compressed/
name: ${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz
retention-days: 10
create-release-draft-if-not-exist:
if: github.ref_type == 'tag'
permissions:
contents: write
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create release draft
uses: topheman/create-release-if-not-exist@v1
with:
args: ${{ github.ref_name }} --draft --generate-notes
upload-artifacts-to-release-draft:
if: github.ref_type == 'tag'
permissions:
contents: write
needs: create-release-draft-if-not-exist
env:
RELEASE_NAME: ${{ github.ref_name }}
name: Upload artifacts to release draft ${{ matrix.platform.target }}
strategy:
matrix:
platform:
- target: x86_64-unknown-linux-gnu
- target: aarch64-unknown-linux-gnu
- target: x86_64-apple-darwin
- target: aarch64-apple-darwin
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Restore cached compressed binaries
uses: actions/cache/restore@v4
with:
path: ./compressed
key: ${{ matrix.platform.target }}-compressed-binaries-${{ github.sha }}
- name: Upload artifacts to release draft
run: |
gh release upload ${{ env.RELEASE_NAME }} ./compressed/${{ env.BINARY_NAME }}-${{ matrix.platform.target }}.tar.gz
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 11 additions & 4 deletions .github/workflows/rust-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,22 @@ jobs:
contents: write
runs-on: ubuntu-latest
needs: build-and-test
env:
RELEASE_NAME: ${{ github.ref_name }}
steps:
- uses: actions/checkout@v4
- name: Restore cached wasm files
id: cache-wasm-files-restore
uses: actions/cache/restore@v4
with:
path: ./tmp/plugins
key: ${{ runner.os }}-wasm-files-${{ github.sha }}
- name: Release draft
uses: softprops/action-gh-release@v2
- name: Create release draft if it doesn't exist
uses: topheman/create-release-if-not-exist@v1
with:
draft: true
files: ./tmp/plugins/*.wasm
args: ${{ env.RELEASE_NAME }} --draft --generate-notes
- name: Upload wasm files to release draft
run: |
gh release upload ${{ env.RELEASE_NAME }} ./tmp/plugins/*.wasm
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37 changes: 37 additions & 0 deletions .github/workflows/update-homebrew-tap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Update Homebrew Tap

on:
release:
types: [published]

jobs:
update-homebrew-tap:
runs-on: ubuntu-latest

permissions:
contents: write # ✅ minimal required for pushing commits

steps:
- name: Checkout source repo
uses: actions/checkout@v4
- name: Update Homebrew Formula
uses: topheman/update-homebrew-tap@v2
with:
formula-target-repository: topheman/homebrew-tap
formula-target-file: Formula/pluginlab.rb
tar-files: |
{
"linuxArm": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-aarch64-unknown-linux-gnu.tar.gz",
"linuxIntel": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-x86_64-unknown-linux-gnu.tar.gz",
"macArm": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-aarch64-apple-darwin.tar.gz",
"macIntel": "https://github.com/topheman/webassembly-component-model-experiments/releases/download/${{ github.ref_name }}/pluginlab-x86_64-apple-darwin.tar.gz"
}
metadata: |
{
"version": "${{ github.ref_name }}",
"binaryName": "pluginlab",
"description": "Terminal REPL with sandboxed multi-language plugin system - unified codebase runs in CLI (Rust) and web (TypeScript)",
"homepage": "https://github.com/topheman/webassembly-component-model-experiments",
"license": "MIT"
}
github-token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,20 @@ In the last seven years I've done a few projects involving rust and WebAssembly:

### pluginlab (rust) - REPL cli host

#### Install
#### Install the binary

**Using cargo (from source)**

```bash
# Install the pluginlab binary
cargo install pluginlab
```

**Using homebrew**

```bash
brew install topheman/tap/pluginlab
```

#### Run

```bash
Expand Down
1 change: 1 addition & 0 deletions crates/pluginlab/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ clap = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
reqwest = "0.12.20"
clap_complete = "4.5"

[build-dependencies]
wasmtime = { workspace = true }
Expand Down
8 changes: 8 additions & 0 deletions crates/pluginlab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,18 @@ More details on the github repo: [topheman/webassembly-component-model-experimen

## Install

**Using cargo (from source)**

```bash
cargo install pluginlab
```

**Using homebrew**

```bash
brew install topheman/tap/pluginlab
```

## Usage

Run the CLI host, loading the latest versions of the plugins from the web (you can also load them from local files).
Expand Down
24 changes: 22 additions & 2 deletions crates/pluginlab/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use clap::Parser;
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,

/// Paths or URLs to WebAssembly plugin files
#[arg(long)]
pub plugins: Vec<String>,

/// Path or URL to WebAssembly REPL logic file
#[arg(long)]
pub repl_logic: String,
pub repl_logic: Option<String>,

#[arg(long, default_value_t = false)]
pub debug: bool,
Expand Down Expand Up @@ -46,3 +49,20 @@ pub struct Cli {
)]
pub allow_all: bool,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
/// Generate completions for your own shell (shipped with the homebrew version)
GenerateCompletions {
/// Specify which shell you target - accepted values: bash, fish, zsh
#[arg(long, value_enum)]
shell: AvailableShells,
},
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum AvailableShells {
Bash,
Fish,
Zsh,
}
65 changes: 59 additions & 6 deletions crates/pluginlab/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,68 @@ pub(crate) use wasm_host::WasmHost;
use anyhow::Result;
use api::host_api::repl::api::transport;
use clap::Parser;
use cli::Cli;
use cli::{Cli, Commands};
use helpers::{StatusHandler, StdoutHandler};
use std::io::Write;

/// Main entry point for the REPL application
pub async fn run_async() -> Result<()> {
// Parse command line arguments
let cli = Cli::parse();

// Handle subcommands first
if let Some(command) = &cli.command {
match command {
Commands::GenerateCompletions { shell } => {
return handle_generate_completions(*shell);
}
}
}

// For REPL mode, repl_logic is required
let repl_logic = cli
.repl_logic
.ok_or_else(|| anyhow::anyhow!("--repl-logic is required when running in REPL mode"))?;

let debug = cli.debug;
let plugins = cli.plugins;
let dir = cli.dir;
let allow_net = cli.allow_net;
let allow_read = cli.allow_read;
let allow_write = cli.allow_write;
let allow_all = cli.allow_all;

// Create a new CLI struct for the remaining operations
let repl_cli = Cli {
command: None,
plugins,
repl_logic: Some(repl_logic.clone()),
debug,
dir,
allow_net,
allow_read,
allow_write,
allow_all,
};

println!("[Host] Starting REPL host...");

// Create a WASI context for the host
// Binding stdio, args, env, preopened dir ...
let wasi_ctx = WasmEngine::build_wasi_ctx(&cli)?;
let wasi_ctx = WasmEngine::build_wasi_ctx(&repl_cli)?;

// Create the WebAssembly engine
let engine = WasmEngine::new()?;

// Create the host
let mut host = WasmHost::new(&engine, wasi_ctx, &cli);
let mut host = WasmHost::new(&engine, wasi_ctx, &repl_cli);

println!("[Host] Loading REPL logic from: {}", cli.repl_logic);
println!("[Host] Loading REPL logic from: {}", repl_logic);
// Override the REPL logic in the binary with the one passed by params
host.load_repl_logic(&engine, &cli.repl_logic).await?;
host.load_repl_logic(&engine, &repl_logic).await?;

// Load plugins
for plugin_source in &cli.plugins {
for plugin_source in &repl_cli.plugins {
println!("[Host] Loading plugin: {}", plugin_source);
host.load_plugin(&engine, plugin_source).await?;
}
Expand Down Expand Up @@ -216,3 +251,21 @@ pub async fn run_async() -> Result<()> {
}
}
}

/// Handle the generate-completions subcommand
fn handle_generate_completions(shell: cli::AvailableShells) -> Result<()> {
use clap::CommandFactory;
use clap_complete::{generate, Shell};
use cli::Cli;

let mut cmd = Cli::command();
let shell_type = match shell {
cli::AvailableShells::Bash => Shell::Bash,
cli::AvailableShells::Fish => Shell::Fish,
cli::AvailableShells::Zsh => Shell::Zsh,
};

generate(shell_type, &mut cmd, "pluginlab", &mut std::io::stdout());

Ok(())
}
Loading