mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-23 16:14:53 +01:00
Compare commits
8 Commits
yaak-api-0
...
yaak-cli-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e7e1232da | ||
|
|
c31d477a90 | ||
|
|
443e1b8262 | ||
|
|
c6b7cb2e32 | ||
|
|
4aef826a80 | ||
|
|
50c7992b42 | ||
|
|
5e9aebda6f | ||
|
|
a1e84c7785 |
57
.github/workflows/release-cli-npm.yml
vendored
57
.github/workflows/release-cli-npm.yml
vendored
@@ -14,8 +14,44 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prepare-vendored-assets:
|
||||
name: Prepare vendored plugin assets
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build plugin assets
|
||||
env:
|
||||
SKIP_WASM_BUILD: "1"
|
||||
run: |
|
||||
npm run build
|
||||
npm run vendor:vendor-plugins
|
||||
|
||||
- name: Upload vendored assets
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vendored-assets
|
||||
path: |
|
||||
crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs
|
||||
crates-tauri/yaak-app/vendored/plugins
|
||||
if-no-files-found: error
|
||||
|
||||
build-binaries:
|
||||
name: Build ${{ matrix.pkg }}
|
||||
needs: prepare-vendored-assets
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -67,6 +103,27 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libdbus-1-dev
|
||||
|
||||
- name: Download vendored assets
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: vendored-assets
|
||||
path: crates-tauri/yaak-app/vendored
|
||||
|
||||
- name: Set CLI build version
|
||||
shell: bash
|
||||
env:
|
||||
WORKFLOW_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
VERSION="$WORKFLOW_VERSION"
|
||||
else
|
||||
VERSION="${GITHUB_REF_NAME#yaak-cli-}"
|
||||
fi
|
||||
VERSION="${VERSION#v}"
|
||||
echo "Building yaak version: $VERSION"
|
||||
echo "YAAK_CLI_VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build yaak
|
||||
run: cargo build --locked --release -p yaak-cli --bin yaak --target ${{ matrix.target }}
|
||||
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -10149,6 +10149,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"futures",
|
||||
"hex",
|
||||
"include_dir",
|
||||
"keyring",
|
||||
"log 0.4.29",
|
||||
"oxc_resolver",
|
||||
|
||||
@@ -16,6 +16,7 @@ dirs = "6"
|
||||
env_logger = "0.11"
|
||||
futures = "0.3"
|
||||
hex = { workspace = true }
|
||||
include_dir = "0.7"
|
||||
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||
log = { workspace = true }
|
||||
rand = "0.8"
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::path::PathBuf;
|
||||
#[derive(Parser)]
|
||||
#[command(name = "yaak")]
|
||||
#[command(about = "Yaak CLI - API client from the command line")]
|
||||
#[command(version)]
|
||||
#[command(version = crate::version::cli_version())]
|
||||
pub struct Cli {
|
||||
/// Use a custom data directory
|
||||
#[arg(long, global = true)]
|
||||
@@ -154,6 +154,10 @@ pub enum RequestCommands {
|
||||
Schema {
|
||||
#[arg(value_enum)]
|
||||
request_type: RequestSchemaType,
|
||||
|
||||
/// Pretty-print schema JSON output
|
||||
#[arg(long)]
|
||||
pretty: bool,
|
||||
},
|
||||
|
||||
/// Create a new HTTP request
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::cli::{AuthArgs, AuthCommands};
|
||||
use crate::ui;
|
||||
use crate::utils::http;
|
||||
use base64::Engine as _;
|
||||
use keyring::Entry;
|
||||
use rand::RngCore;
|
||||
@@ -136,10 +137,8 @@ async fn whoami() -> CommandResult {
|
||||
};
|
||||
|
||||
let url = format!("{}/api/v1/whoami", environment.api_base_url());
|
||||
let response = reqwest::Client::new()
|
||||
let response = http::build_client(Some(&token))?
|
||||
.get(url)
|
||||
.header("X-Yaak-Session", token)
|
||||
.header(reqwest::header::USER_AGENT, user_agent())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to call whoami endpoint: {e}"))?;
|
||||
@@ -156,7 +155,7 @@ async fn whoami() -> CommandResult {
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
return Err(parse_api_error(status.as_u16(), &body));
|
||||
return Err(http::parse_api_error(status.as_u16(), &body));
|
||||
}
|
||||
|
||||
println!("{body}");
|
||||
@@ -342,9 +341,8 @@ async fn write_redirect(stream: &mut TcpStream, location: &str) -> std::io::Resu
|
||||
}
|
||||
|
||||
async fn exchange_access_token(oauth: &OAuthFlow, code: &str) -> CommandResult<String> {
|
||||
let response = reqwest::Client::new()
|
||||
let response = http::build_client(None)?
|
||||
.post(&oauth.token_url)
|
||||
.header(reqwest::header::USER_AGENT, user_agent())
|
||||
.form(&[
|
||||
("grant_type", "authorization_code"),
|
||||
("client_id", OAUTH_CLIENT_ID),
|
||||
@@ -406,38 +404,12 @@ fn delete_auth_token(environment: Environment) -> CommandResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_api_error(status: u16, body: &str) -> String {
|
||||
if let Ok(value) = serde_json::from_str::<Value>(body) {
|
||||
if let Some(message) = value.get("message").and_then(Value::as_str) {
|
||||
return message.to_string();
|
||||
}
|
||||
if let Some(error) = value.get("error").and_then(Value::as_str) {
|
||||
return error.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
format!("API error {status}: {body}")
|
||||
}
|
||||
|
||||
fn random_hex(bytes: usize) -> String {
|
||||
let mut data = vec![0_u8; bytes];
|
||||
OsRng.fill_bytes(&mut data);
|
||||
hex::encode(data)
|
||||
}
|
||||
|
||||
fn user_agent() -> String {
|
||||
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
|
||||
}
|
||||
|
||||
fn ua_platform() -> &'static str {
|
||||
match std::env::consts::OS {
|
||||
"windows" => "Win",
|
||||
"darwin" => "Mac",
|
||||
"linux" => "Linux",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm_open_browser() -> CommandResult<bool> {
|
||||
if !io::stdin().is_terminal() {
|
||||
return Ok(true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::cli::{GenerateArgs, PluginArgs, PluginCommands, PluginPathArg};
|
||||
use crate::ui;
|
||||
use crate::utils::http;
|
||||
use keyring::Entry;
|
||||
use rand::Rng;
|
||||
use rolldown::{
|
||||
@@ -7,7 +8,6 @@ use rolldown::{
|
||||
WatchOption, Watcher,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
use std::io::{self, IsTerminal, Read, Write};
|
||||
@@ -186,10 +186,8 @@ async fn publish(args: PluginPathArg) -> CommandResult {
|
||||
|
||||
ui::info("Uploading plugin");
|
||||
let url = format!("{}/api/v1/plugins/publish", environment.api_base_url());
|
||||
let response = reqwest::Client::new()
|
||||
let response = http::build_client(Some(&token))?
|
||||
.post(url)
|
||||
.header("X-Yaak-Session", token)
|
||||
.header(reqwest::header::USER_AGENT, user_agent())
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/zip")
|
||||
.body(archive)
|
||||
.send()
|
||||
@@ -201,7 +199,7 @@ async fn publish(args: PluginPathArg) -> CommandResult {
|
||||
response.text().await.map_err(|e| format!("Failed reading publish response body: {e}"))?;
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(parse_api_error(status.as_u16(), &body));
|
||||
return Err(http::parse_api_error(status.as_u16(), &body));
|
||||
}
|
||||
|
||||
let published: PublishResponse = serde_json::from_str(&body)
|
||||
@@ -389,32 +387,6 @@ fn get_auth_token(environment: Environment) -> CommandResult<Option<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_api_error(status: u16, body: &str) -> String {
|
||||
if let Ok(value) = serde_json::from_str::<Value>(body) {
|
||||
if let Some(message) = value.get("message").and_then(Value::as_str) {
|
||||
return message.to_string();
|
||||
}
|
||||
if let Some(error) = value.get("error").and_then(Value::as_str) {
|
||||
return error.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
format!("API error {status}: {body}")
|
||||
}
|
||||
|
||||
fn user_agent() -> String {
|
||||
format!("YaakCli/{} ({})", env!("CARGO_PKG_VERSION"), ua_platform())
|
||||
}
|
||||
|
||||
fn ua_platform() -> &'static str {
|
||||
match std::env::consts::OS {
|
||||
"windows" => "Win",
|
||||
"darwin" => "Mac",
|
||||
"linux" => "Linux",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
fn random_name() -> String {
|
||||
const ADJECTIVES: &[&str] = &[
|
||||
"young", "youthful", "yellow", "yielding", "yappy", "yawning", "yummy", "yucky", "yearly",
|
||||
|
||||
@@ -35,8 +35,8 @@ pub async fn run(
|
||||
}
|
||||
};
|
||||
}
|
||||
RequestCommands::Schema { request_type } => {
|
||||
return match schema(ctx, request_type).await {
|
||||
RequestCommands::Schema { request_type, pretty } => {
|
||||
return match schema(ctx, request_type, pretty).await {
|
||||
Ok(()) => 0,
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error}");
|
||||
@@ -75,7 +75,7 @@ fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandResult {
|
||||
async fn schema(ctx: &CliContext, request_type: RequestSchemaType, pretty: bool) -> CommandResult {
|
||||
let mut schema = match request_type {
|
||||
RequestSchemaType::Http => serde_json::to_value(schema_for!(HttpRequest))
|
||||
.map_err(|e| format!("Failed to serialize HTTP request schema: {e}"))?,
|
||||
@@ -85,16 +85,53 @@ async fn schema(ctx: &CliContext, request_type: RequestSchemaType) -> CommandRes
|
||||
.map_err(|e| format!("Failed to serialize WebSocket request schema: {e}"))?,
|
||||
};
|
||||
|
||||
enrich_schema_guidance(&mut schema, request_type);
|
||||
|
||||
if let Err(error) = merge_auth_schema_from_plugins(ctx, &mut schema).await {
|
||||
eprintln!("Warning: Failed to enrich authentication schema from plugins: {error}");
|
||||
}
|
||||
|
||||
let output = serde_json::to_string_pretty(&schema)
|
||||
let output = if pretty {
|
||||
serde_json::to_string_pretty(&schema)
|
||||
} else {
|
||||
serde_json::to_string(&schema)
|
||||
}
|
||||
.map_err(|e| format!("Failed to format schema JSON: {e}"))?;
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enrich_schema_guidance(schema: &mut Value, request_type: RequestSchemaType) {
|
||||
if !matches!(request_type, RequestSchemaType::Http) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(url_schema) = properties.get_mut("url").and_then(Value::as_object_mut) {
|
||||
append_description(
|
||||
url_schema,
|
||||
"For path segments like `/foo/:id/comments/:commentId`, put concrete values in `urlParameters` using names without `:` (for example `id`, `commentId`).",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn append_description(schema: &mut Map<String, Value>, extra: &str) {
|
||||
match schema.get_mut("description") {
|
||||
Some(Value::String(existing)) if !existing.trim().is_empty() => {
|
||||
if !existing.ends_with(' ') {
|
||||
existing.push(' ');
|
||||
}
|
||||
existing.push_str(extra);
|
||||
}
|
||||
_ => {
|
||||
schema.insert("description".to_string(), Value::String(extra.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn merge_auth_schema_from_plugins(
|
||||
ctx: &CliContext,
|
||||
schema: &mut Value,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::plugin_events::CliPluginEventBridge;
|
||||
use include_dir::{Dir, include_dir};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -9,6 +11,13 @@ use yaak_models::query_manager::QueryManager;
|
||||
use yaak_plugins::events::PluginContext;
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
|
||||
const EMBEDDED_PLUGIN_RUNTIME: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs"
|
||||
));
|
||||
static EMBEDDED_VENDORED_PLUGINS: Dir<'_> =
|
||||
include_dir!("$CARGO_MANIFEST_DIR/../../crates-tauri/yaak-app/vendored/plugins");
|
||||
|
||||
pub struct CliContext {
|
||||
data_dir: PathBuf,
|
||||
query_manager: QueryManager,
|
||||
@@ -33,37 +42,32 @@ impl CliContext {
|
||||
let installed_plugin_dir = data_dir.join("installed-plugins");
|
||||
let node_bin_path = PathBuf::from("node");
|
||||
|
||||
prepare_embedded_vendored_plugins(&vendored_plugin_dir)
|
||||
.expect("Failed to prepare bundled plugins");
|
||||
|
||||
let plugin_runtime_main =
|
||||
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../crates-tauri/yaak-app/vendored/plugin-runtime/index.cjs")
|
||||
prepare_embedded_plugin_runtime(&data_dir)
|
||||
.expect("Failed to prepare embedded plugin runtime")
|
||||
});
|
||||
|
||||
let plugin_manager = Arc::new(
|
||||
PluginManager::new(
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
node_bin_path,
|
||||
plugin_runtime_main,
|
||||
false,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
let plugins = query_manager.connect().list_plugins().unwrap_or_default();
|
||||
if !plugins.is_empty() {
|
||||
let errors = plugin_manager
|
||||
.initialize_all_plugins(plugins, &PluginContext::new_empty())
|
||||
.await;
|
||||
for (plugin_dir, error_msg) in errors {
|
||||
eprintln!(
|
||||
"Warning: Failed to initialize plugin '{}': {}",
|
||||
plugin_dir, error_msg
|
||||
);
|
||||
match PluginManager::new(
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
node_bin_path,
|
||||
plugin_runtime_main,
|
||||
&query_manager,
|
||||
&PluginContext::new_empty(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(plugin_manager) => Some(Arc::new(plugin_manager)),
|
||||
Err(err) => {
|
||||
eprintln!("Warning: Failed to initialize plugins: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Some(plugin_manager)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -113,3 +117,17 @@ impl CliContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_embedded_plugin_runtime(data_dir: &Path) -> std::io::Result<PathBuf> {
|
||||
let runtime_dir = data_dir.join("vendored").join("plugin-runtime");
|
||||
fs::create_dir_all(&runtime_dir)?;
|
||||
let runtime_main = runtime_dir.join("index.cjs");
|
||||
fs::write(&runtime_main, EMBEDDED_PLUGIN_RUNTIME)?;
|
||||
Ok(runtime_main)
|
||||
}
|
||||
|
||||
fn prepare_embedded_vendored_plugins(vendored_plugin_dir: &Path) -> std::io::Result<()> {
|
||||
fs::create_dir_all(vendored_plugin_dir)?;
|
||||
EMBEDDED_VENDORED_PLUGINS.extract(vendored_plugin_dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ mod context;
|
||||
mod plugin_events;
|
||||
mod ui;
|
||||
mod utils;
|
||||
mod version;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, RequestCommands};
|
||||
|
||||
47
crates-cli/yaak-cli/src/utils/http.rs
Normal file
47
crates-cli/yaak-cli/src/utils/http.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use reqwest::Client;
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, USER_AGENT};
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn build_client(session_token: Option<&str>) -> Result<Client, String> {
|
||||
let mut headers = HeaderMap::new();
|
||||
let user_agent = HeaderValue::from_str(&user_agent())
|
||||
.map_err(|e| format!("Failed to build user-agent header: {e}"))?;
|
||||
headers.insert(USER_AGENT, user_agent);
|
||||
|
||||
if let Some(token) = session_token {
|
||||
let token_value = HeaderValue::from_str(token)
|
||||
.map_err(|e| format!("Failed to build session header: {e}"))?;
|
||||
headers.insert(HeaderName::from_static("x-yaak-session"), token_value);
|
||||
}
|
||||
|
||||
Client::builder()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to initialize HTTP client: {e}"))
|
||||
}
|
||||
|
||||
pub fn parse_api_error(status: u16, body: &str) -> String {
|
||||
if let Ok(value) = serde_json::from_str::<Value>(body) {
|
||||
if let Some(message) = value.get("message").and_then(Value::as_str) {
|
||||
return message.to_string();
|
||||
}
|
||||
if let Some(error) = value.get("error").and_then(Value::as_str) {
|
||||
return error.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
format!("API error {status}: {body}")
|
||||
}
|
||||
|
||||
fn user_agent() -> String {
|
||||
format!("YaakCli/{} ({})", crate::version::cli_version(), ua_platform())
|
||||
}
|
||||
|
||||
fn ua_platform() -> &'static str {
|
||||
match std::env::consts::OS {
|
||||
"windows" => "Win",
|
||||
"darwin" => "Mac",
|
||||
"linux" => "Linux",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod confirm;
|
||||
pub mod http;
|
||||
pub mod json;
|
||||
|
||||
3
crates-cli/yaak-cli/src/version.rs
Normal file
3
crates-cli/yaak-cli/src/version.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub fn cli_version() -> &'static str {
|
||||
option_env!("YAAK_CLI_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
|
||||
}
|
||||
@@ -189,6 +189,21 @@ fn request_schema_http_outputs_json_schema() {
|
||||
.args(["request", "schema", "http"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(contains("\"type\":\"object\""))
|
||||
.stdout(contains("\"authentication\":"))
|
||||
.stdout(contains("/foo/:id/comments/:commentId"))
|
||||
.stdout(contains("put concrete values in `urlParameters`"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_schema_http_pretty_prints_with_flag() {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let data_dir = temp_dir.path();
|
||||
|
||||
cli_cmd(data_dir)
|
||||
.args(["request", "schema", "http", "--pretty"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(contains("\"type\": \"object\""))
|
||||
.stdout(contains("\"authentication\""));
|
||||
}
|
||||
|
||||
@@ -23,12 +23,11 @@ use tokio::sync::Mutex;
|
||||
use ts_rs::TS;
|
||||
use yaak_api::yaak_api_client;
|
||||
use yaak_models::models::Plugin;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::api::{
|
||||
PluginNameVersion, PluginSearchResponse, PluginUpdatesResponse, check_plugin_updates,
|
||||
search_plugins,
|
||||
};
|
||||
use yaak_plugins::events::{Color, Icon, PluginContext, ShowToastRequest};
|
||||
use yaak_plugins::events::PluginContext;
|
||||
use yaak_plugins::install::{delete_and_uninstall, download_and_install};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_meta::get_plugin_meta;
|
||||
@@ -268,6 +267,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.join("index.cjs");
|
||||
|
||||
let dev_mode = is_dev();
|
||||
let query_manager =
|
||||
app_handle.state::<yaak_models::query_manager::QueryManager>().inner().clone();
|
||||
|
||||
// Create plugin manager asynchronously
|
||||
let app_handle_clone = app_handle.clone();
|
||||
@@ -277,53 +278,12 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
installed_plugin_dir,
|
||||
node_bin_path,
|
||||
plugin_runtime_main,
|
||||
&query_manager,
|
||||
&PluginContext::new_empty(),
|
||||
dev_mode,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Initialize all plugins after manager is created
|
||||
let bundled_dirs = manager
|
||||
.list_bundled_plugin_dirs()
|
||||
.await
|
||||
.expect("Failed to list bundled plugins");
|
||||
|
||||
// Ensure all bundled plugins make it into the database
|
||||
let db = app_handle_clone.db();
|
||||
for dir in &bundled_dirs {
|
||||
if db.get_plugin_by_directory(dir).is_none() {
|
||||
db.upsert_plugin(
|
||||
&Plugin {
|
||||
directory: dir.clone(),
|
||||
enabled: true,
|
||||
url: None,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)
|
||||
.expect("Failed to upsert bundled plugin");
|
||||
}
|
||||
}
|
||||
|
||||
// Get all plugins from database and initialize
|
||||
let plugins = db.list_plugins().expect("Failed to list plugins from database");
|
||||
drop(db); // Explicitly drop the connection before await
|
||||
|
||||
let errors =
|
||||
manager.initialize_all_plugins(plugins, &PluginContext::new_empty()).await;
|
||||
|
||||
// Show toast for any failed plugins
|
||||
for (plugin_dir, error_msg) in errors {
|
||||
let plugin_name = plugin_dir.split('/').last().unwrap_or(&plugin_dir);
|
||||
let toast = ShowToastRequest {
|
||||
message: format!("Failed to start plugin '{}': {}", plugin_name, error_msg),
|
||||
color: Some(Color::Danger),
|
||||
icon: Some(Icon::AlertTriangle),
|
||||
timeout: Some(10000),
|
||||
};
|
||||
if let Err(emit_err) = app_handle_clone.emit("show_toast", toast) {
|
||||
error!("Failed to emit toast for plugin error: {emit_err:?}");
|
||||
}
|
||||
}
|
||||
.await
|
||||
.expect("Failed to initialize plugins");
|
||||
|
||||
app_handle_clone.manage(manager);
|
||||
});
|
||||
|
||||
@@ -611,6 +611,8 @@ pub struct Environment {
|
||||
pub base: bool,
|
||||
pub parent_model: String,
|
||||
pub parent_id: Option<String>,
|
||||
/// Variables defined in this environment scope.
|
||||
/// Child environments override parent variables by name.
|
||||
pub variables: Vec<EnvironmentVariable>,
|
||||
pub color: Option<String>,
|
||||
pub sort_priority: f64,
|
||||
@@ -845,6 +847,8 @@ pub struct HttpUrlParameter {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
pub enabled: bool,
|
||||
/// Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
|
||||
/// Other entries are appended as query parameters
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
#[ts(optional, as = "Option<String>")]
|
||||
@@ -877,6 +881,7 @@ pub struct HttpRequest {
|
||||
pub name: String,
|
||||
pub sort_priority: f64,
|
||||
pub url: String,
|
||||
/// URL parameters used for both path placeholders (`:id`) and query string entries.
|
||||
pub url_parameters: Vec<HttpUrlParameter>,
|
||||
}
|
||||
|
||||
@@ -1118,6 +1123,7 @@ pub struct WebsocketRequest {
|
||||
pub name: String,
|
||||
pub sort_priority: f64,
|
||||
pub url: String,
|
||||
/// URL parameters used for both path placeholders (`:id`) and query string entries.
|
||||
pub url_parameters: Vec<HttpUrlParameter>,
|
||||
}
|
||||
|
||||
@@ -1728,6 +1734,7 @@ pub struct GrpcRequest {
|
||||
pub name: String,
|
||||
pub service: Option<String>,
|
||||
pub sort_priority: f64,
|
||||
/// Server URL (http for plaintext or https for secure)
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ use tokio::sync::mpsc::error::TrySendError;
|
||||
use tokio::sync::{Mutex, mpsc, oneshot};
|
||||
use tokio::time::{Instant, timeout};
|
||||
use yaak_models::models::Plugin;
|
||||
use yaak_models::util::generate_id;
|
||||
use yaak_models::query_manager::QueryManager;
|
||||
use yaak_models::util::{UpdateSource, generate_id};
|
||||
use yaak_templates::error::Error::RenderError;
|
||||
use yaak_templates::error::Result as TemplateResult;
|
||||
|
||||
@@ -61,14 +62,18 @@ impl PluginManager {
|
||||
/// * `installed_plugin_dir` - Path to installed plugins directory
|
||||
/// * `node_bin_path` - Path to the yaaknode binary
|
||||
/// * `plugin_runtime_main` - Path to the plugin runtime index.cjs
|
||||
/// * `query_manager` - Query manager for bundled plugin registration and loading
|
||||
/// * `plugin_context` - Context to use while initializing plugins
|
||||
/// * `dev_mode` - Whether the app is in dev mode (affects plugin loading)
|
||||
pub async fn new(
|
||||
vendored_plugin_dir: PathBuf,
|
||||
installed_plugin_dir: PathBuf,
|
||||
node_bin_path: PathBuf,
|
||||
plugin_runtime_main: PathBuf,
|
||||
query_manager: &QueryManager,
|
||||
plugin_context: &PluginContext,
|
||||
dev_mode: bool,
|
||||
) -> PluginManager {
|
||||
) -> Result<PluginManager> {
|
||||
let (events_tx, mut events_rx) = mpsc::channel(2048);
|
||||
let (kill_server_tx, kill_server_rx) = tokio::sync::watch::channel(false);
|
||||
let (killed_tx, killed_rx) = oneshot::channel();
|
||||
@@ -151,12 +156,40 @@ impl PluginManager {
|
||||
&kill_server_rx,
|
||||
killed_tx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
info!("Waiting for plugins to initialize");
|
||||
init_plugins_task.await.unwrap();
|
||||
init_plugins_task.await.map_err(|e| PluginErr(e.to_string()))?;
|
||||
|
||||
plugin_manager
|
||||
let bundled_dirs = plugin_manager.list_bundled_plugin_dirs().await?;
|
||||
let db = query_manager.connect();
|
||||
for dir in bundled_dirs {
|
||||
if db.get_plugin_by_directory(&dir).is_none() {
|
||||
db.upsert_plugin(
|
||||
&Plugin {
|
||||
directory: dir,
|
||||
enabled: true,
|
||||
url: None,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
let plugins = db.list_plugins()?;
|
||||
drop(db);
|
||||
|
||||
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
|
||||
if !init_errors.is_empty() {
|
||||
let joined = init_errors
|
||||
.into_iter()
|
||||
.map(|(dir, err)| format!("{dir}: {err}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
|
||||
}
|
||||
|
||||
Ok(plugin_manager)
|
||||
}
|
||||
|
||||
/// Get the vendored plugin directory path (resolves dev mode path if applicable)
|
||||
|
||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -73,7 +73,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0-beta.1",
|
||||
"@yaakapp/cli": "^0.4.0-beta.2",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
@@ -4326,9 +4326,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp/cli": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-Q/nRjS9nSNZy8PSBJ8VfczwSmfK4k/s9Co5YnsCiomyFppDiIR4hGjwwXAZDjcjnVZYrzAboRM2BXMjJ9PfZCA==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-UXPxTS9oWVCIr4rShC7HjcAX+gSmw/BQ5F1Xp3Rub3vY/G7+513JJsc1HhLGVZqFfOVRSMEKRxtF9/9okSyiHg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -4336,18 +4336,18 @@
|
||||
"yaakcli": "bin/cli.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.1",
|
||||
"@yaakapp/cli-darwin-x64": "0.4.0-beta.1",
|
||||
"@yaakapp/cli-linux-arm64": "0.4.0-beta.1",
|
||||
"@yaakapp/cli-linux-x64": "0.4.0-beta.1",
|
||||
"@yaakapp/cli-win32-arm64": "0.4.0-beta.1",
|
||||
"@yaakapp/cli-win32-x64": "0.4.0-beta.1"
|
||||
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-darwin-x64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-linux-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-linux-x64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-win32-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-win32-x64": "0.4.0-beta.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-arm64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-afvIQeT35bI6d6fRyJ6hnfr0FnzajL4wiVPniezXXEFsVjG74/FPB7jYHRTnIVwG+tPziOND1RG1ff3Hle/Duw==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-mqkyH5tIPRLs9JumP9ZmzjB5gIwmOL1yCDoJ1qVU8DIJ7mwlcQaPGYTK98pVdBcKOjofVakBTcpol9P8rBv4qw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4358,9 +4358,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-x64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-4j2AwBnbmVgbzkqLDEZtSQ+/PvJ/eo6GecJcBW92YWnwR4+/R5vPT87Pd0Dy2L4X7Hy2VVmNbwNAEOVvef+u6g==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-QI/H2yUF8CkJq+cnRthoUWWTEJPH4QPA78FYcGjFRhvBaj1m2G/GlCA5NkTXm/fvIjNkQEODSihXrhU+zoSSCw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4371,9 +4371,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-arm64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-WgqeTcj7BIgCF1chunX/XcxmpArftYATO1q61aNPxNxIVDKVqbbOh/rLByvwFM8q9A49OjgcLI4QQT1CWdBLig==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-nvAp97LkgRpqVHyMwDdpkzlKOWG2kJXezCLRZaRWaEpbnNuviSF+0yzCuFGZRHEEspj7B0TiM+sKGkpvjNlweA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4384,9 +4384,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-x64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-eMN7CiTbB4pH5NIHTGqNiv56PXb+V7cGg/yU+FopRk69ETH1n+cwGlx1UxSUlcLnaxx0s6pPoo3e+C4cq+i0BQ==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-9/qAMNrtE9glxih3XWGfFssIJpQ4mHNUTuWYKroc0aZZUrunnCw3tX1tQtFDxy0QRIZcGlBeBRtgxuuBd2fYbg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4397,9 +4397,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-arm64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-4ygqyEeHLNlTAWYpg83SuLK9dx1af6HqSfHnWFBigflENdZejD/oSGNr1XZeB61QQnjlvaJaqENs4BS9UI9piA==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-eM1zL+hl0y3NBLxWO90y9VyaFsAf0HAsECBWvhKhvEdd6KG4K1XzpXrC30cHQBGePIrCa/az8eSuvTde0Z2C/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4410,9 +4410,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-x64": {
|
||||
"version": "0.4.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.1.tgz",
|
||||
"integrity": "sha512-Xpxk+e9RWKOzY9siMDlgZPa0HU61GsTn5CTHOpPxUJHmUu+7urJ+sEgaoZx4fRjPBH+FVD9Y4s+zRCawd7O75w==",
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-ySdiK0h216EqURkM5KZoqbPTgbIX4eNK/IgrKwSazxRb369HOZYQ8X68as+VRxEL4NCMmWlQNdbBDuf+apg/mg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
"app-dev": "node scripts/run-dev.mjs",
|
||||
"migration": "node scripts/create-migration.cjs",
|
||||
"build": "npm run --workspaces --if-present build",
|
||||
"build-plugins": "npm run --workspaces --if-present build",
|
||||
"test": "npm run --workspaces --if-present test",
|
||||
"icons": "run-p icons:*",
|
||||
"icons:dev": "tauri icon crates-tauri/yaak-app/icons/icon-dev.png --output crates-tauri/yaak-app/icons/dev",
|
||||
@@ -98,7 +97,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0-beta.1",
|
||||
"@yaakapp/cli": "^0.4.0-beta.2",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { readdirSync, cpSync, existsSync } = require('node:fs');
|
||||
const { readdirSync, cpSync, existsSync, mkdirSync } = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const pluginsDir = path.join(__dirname, '..', 'plugins');
|
||||
@@ -24,6 +24,7 @@ for (const name of readdirSync(pluginsDir)) {
|
||||
continue;
|
||||
}
|
||||
const destDir = path.join(__dirname, '../crates-tauri/yaak-app/vendored/plugins/', name);
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
console.log(`Copying ${name} to ${destDir}`);
|
||||
cpSync(path.join(dir, 'package.json'), path.join(destDir, 'package.json'));
|
||||
cpSync(path.join(dir, 'build'), path.join(destDir, 'build'), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user