mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-24 19:44:53 +01:00
Compare commits
3 Commits
v2025.6.0-
...
v2025.6.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe50959b9 | ||
|
|
523e7dcf16 | ||
|
|
7951f3a7bd |
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -72,6 +72,9 @@ jobs:
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run bootstrap
|
||||
run: npm run bootstrap
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "eslint . --ext .ts,.tsx"
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.8.1"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"yaml": "^2.4.2"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"openapi-to-postmanv2": "^5.0.0",
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,22 @@ export function migrateImport(contents: string) {
|
||||
}
|
||||
}
|
||||
|
||||
return { resources: parsed.resources }; // Should already be in the correct format
|
||||
// Migrate v4 to v5
|
||||
for (const environment of parsed.resources.environments ?? []) {
|
||||
if ('base' in environment && environment.base) {
|
||||
environment.parentId = environment.workspaceId;
|
||||
environment.parentType = 'workspace';
|
||||
delete environment.environmentId;
|
||||
} else if ('base' in environment && !environment.base) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const baseEnvironment = parsed.resources.environments.find((e: any) => e.base);
|
||||
environment.parentId = baseEnvironment?.id ?? null;
|
||||
environment.parentType = 'environment';
|
||||
delete environment.environmentId;
|
||||
}
|
||||
}
|
||||
|
||||
return { resources: parsed.resources };
|
||||
}
|
||||
|
||||
function isJSObject(obj: unknown) {
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0"
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^11.1.0"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"eslint . --ext .ts,.tsx"
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,49 @@ import type { PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
themes: [
|
||||
{
|
||||
id: 'high-contrast',
|
||||
label: 'High Contrast Light',
|
||||
dark: false,
|
||||
base: {
|
||||
surface: 'white',
|
||||
surfaceHighlight: 'hsl(218,24%,93%)',
|
||||
text: 'black',
|
||||
textSubtle: 'hsl(217,24%,40%)',
|
||||
textSubtlest: 'hsl(217,24%,40%)',
|
||||
border: 'hsl(217,22%,50%)',
|
||||
borderSubtle: 'hsl(217,22%,60%)',
|
||||
primary: 'hsl(267,67%,47%)',
|
||||
secondary: 'hsl(218,18%,53%)',
|
||||
info: 'hsl(206,100%,36%)',
|
||||
success: 'hsl(155,100%,26%)',
|
||||
notice: 'hsl(45,100%,31%)',
|
||||
warning: 'hsl(30,99%,34%)',
|
||||
danger: 'hsl(334,100%,35%)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'high-contrast-dark',
|
||||
label: 'High Contrast Dark',
|
||||
dark: true,
|
||||
base: {
|
||||
surface: 'hsl(0,0%,0%)',
|
||||
surfaceHighlight: 'hsl(0,0%,20%)',
|
||||
text: 'hsl(0,0%,100%)',
|
||||
textSubtle: 'hsl(0,0%,90%)',
|
||||
textSubtlest: 'hsl(0,0%,80%)',
|
||||
selection: 'hsl(276,100%,30%)',
|
||||
surfaceActive: 'hsl(276,100%,30%)',
|
||||
border: 'hsl(0,0%,60%)',
|
||||
primary: 'hsl(266,100%,85%)',
|
||||
secondary: 'hsl(242,20%,72%)',
|
||||
info: 'hsl(208,100%,83%)',
|
||||
success: 'hsl(150,100%,63%)',
|
||||
notice: 'hsl(49,100%,77%)',
|
||||
warning: 'hsl(28,100%,73%)',
|
||||
danger: 'hsl(343,100%,79%)',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'catppuccin-frappe',
|
||||
label: 'Catppuccin Frappé',
|
||||
|
||||
32
src-tauri/Cargo.lock
generated
32
src-tauri/Cargo.lock
generated
@@ -5163,10 +5163,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
@@ -5203,10 +5204,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
name = "serde_core"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5236,6 +5246,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -8162,6 +8183,7 @@ dependencies = [
|
||||
"notify",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_yaml",
|
||||
"sha1",
|
||||
"tauri",
|
||||
|
||||
@@ -26,7 +26,7 @@ impl<'a> DbContext<'a> {
|
||||
let environments = self.list_environments_ensure_base(workspace_id)?;
|
||||
let base_environments = environments
|
||||
.into_iter()
|
||||
.filter(|e| e.parent_id.is_none())
|
||||
.filter(|e| e.parent_model == "workspace")
|
||||
.collect::<Vec<Environment>>();
|
||||
|
||||
if base_environments.len() > 1 {
|
||||
@@ -44,13 +44,14 @@ impl<'a> DbContext<'a> {
|
||||
let mut environments =
|
||||
self.find_many::<Environment>(EnvironmentIden::WorkspaceId, workspace_id, None)?;
|
||||
|
||||
let base_environment = environments.iter().find(|e| e.parent_id.is_none());
|
||||
let base_environment = environments.iter().find(|e| e.parent_model == "workspace");
|
||||
|
||||
if let None = base_environment {
|
||||
let e = self.upsert_environment(
|
||||
&Environment {
|
||||
workspace_id: workspace_id.to_string(),
|
||||
name: "Global Variables".to_string(),
|
||||
parent_model: "workspace".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
|
||||
@@ -19,6 +19,7 @@ thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "sync", "macros"] }
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
yaak-models = { workspace = true }
|
||||
serde_path_to_error = "0.1.20"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::error::Error::UnknownModel;
|
||||
use crate::error::Result;
|
||||
use chrono::NaiveDateTime;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -11,7 +12,7 @@ use yaak_models::models::{
|
||||
AnyModel, Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum SyncModel {
|
||||
@@ -23,6 +24,78 @@ pub enum SyncModel {
|
||||
WebsocketRequest(WebsocketRequest),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SyncModel {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde_path_to_error as spte;
|
||||
let mut v = Value::deserialize(deserializer)?;
|
||||
let model = match v.get("model") {
|
||||
Some(Value::String(model)) => model.clone(),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
let model = model.as_str();
|
||||
|
||||
let obj = v
|
||||
.as_mapping_mut()
|
||||
.ok_or_else(|| serde::de::Error::custom("expected object for SyncModel"))?;
|
||||
|
||||
// Dispatch to CHILD types (no recursion)
|
||||
match model {
|
||||
"workspace" => {
|
||||
let x: Workspace = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::Workspace(x))
|
||||
}
|
||||
"environment" => {
|
||||
migrate_environment(obj);
|
||||
let x: Environment = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::Environment(x))
|
||||
}
|
||||
"folder" => {
|
||||
let x: Folder = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::Folder(x))
|
||||
}
|
||||
"http_request" => {
|
||||
let x: HttpRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::HttpRequest(x))
|
||||
}
|
||||
"grpc_request" => {
|
||||
let x: GrpcRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::GrpcRequest(x))
|
||||
}
|
||||
"websocket_request" => {
|
||||
let x: WebsocketRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
||||
Ok(SyncModel::WebsocketRequest(x))
|
||||
}
|
||||
other => Err(serde::de::Error::unknown_variant(
|
||||
other,
|
||||
&[
|
||||
"workspace",
|
||||
"environment",
|
||||
"folder",
|
||||
"http_request",
|
||||
"grpc_request",
|
||||
"websocket_request",
|
||||
],
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_environment(obj: &mut Mapping) {
|
||||
match obj.get("base") {
|
||||
Some(Value::Bool(base)) => {
|
||||
if *base {
|
||||
obj.insert("parentModel".into(), "workspace".into());
|
||||
} else {
|
||||
obj.insert("parentModel".into(), "environment".into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncModel {
|
||||
pub fn from_bytes(content: Vec<u8>, file_path: &Path) -> Result<Option<(SyncModel, String)>> {
|
||||
let mut hasher = Sha1::new();
|
||||
@@ -145,3 +218,59 @@ impl TryFrom<AnyModel> for SyncModel {
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod placeholder_tests {
|
||||
use crate::error::Result;
|
||||
use crate::models::SyncModel;
|
||||
|
||||
#[test]
|
||||
fn deserializes_environment_via_syncmodel_with_fixups() -> Result<()> {
|
||||
let raw = r#"
|
||||
type: environment
|
||||
model: environment
|
||||
id: ev_fAUS49FUN2
|
||||
workspaceId: wk_kfSI3JDHd7
|
||||
createdAt: 2025-01-11T17:02:58.012792
|
||||
updatedAt: 2025-07-23T20:00:46.049649
|
||||
name: Global Variables
|
||||
public: true
|
||||
base: true
|
||||
variables: []
|
||||
color: null
|
||||
"#;
|
||||
|
||||
let m: SyncModel = serde_yaml::from_str(raw)?;
|
||||
match m {
|
||||
SyncModel::Environment(env) => {
|
||||
assert_eq!(env.parent_model, "workspace".to_string());
|
||||
assert_eq!(env.parent_id, None);
|
||||
}
|
||||
_ => panic!("expected base environment"),
|
||||
}
|
||||
|
||||
let raw = r#"
|
||||
type: environment
|
||||
model: environment
|
||||
id: ev_fAUS49FUN2
|
||||
workspaceId: wk_kfSI3JDHd7
|
||||
createdAt: 2025-01-11T17:02:58.012792
|
||||
updatedAt: 2025-07-23T20:00:46.049649
|
||||
name: Global Variables
|
||||
public: true
|
||||
base: false
|
||||
variables: []
|
||||
color: null
|
||||
"#;
|
||||
let m: SyncModel = serde_yaml::from_str(raw)?;
|
||||
match m {
|
||||
SyncModel::Environment(env) => {
|
||||
assert_eq!(env.parent_model, "environment".to_string());
|
||||
assert_eq!(env.parent_id, None);
|
||||
}
|
||||
_ => panic!("expected sub environment"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
|
||||
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
||||
|
||||
export function EnvironmentEditor({
|
||||
environment: selectedEnvironment,
|
||||
environment,
|
||||
hideName,
|
||||
className,
|
||||
}: {
|
||||
@@ -32,7 +32,7 @@ export function EnvironmentEditor({
|
||||
hideName?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const workspaceId = selectedEnvironment.workspaceId;
|
||||
const workspaceId = environment.workspaceId;
|
||||
const isEncryptionEnabled = useIsEncryptionEnabled();
|
||||
const valueVisibility = useKeyValue<boolean>({
|
||||
namespace: 'global',
|
||||
@@ -41,15 +41,15 @@ export function EnvironmentEditor({
|
||||
});
|
||||
const { allEnvironments } = useEnvironmentsBreakdown();
|
||||
const handleChange = useCallback(
|
||||
(variables: PairWithId[]) => patchModel(selectedEnvironment, { variables }),
|
||||
[selectedEnvironment],
|
||||
(variables: PairWithId[]) => patchModel(environment, { variables }),
|
||||
[environment],
|
||||
);
|
||||
const [forceUpdateKey, regenerateForceUpdateKey] = useRandomKey();
|
||||
|
||||
// Gather a list of env names from other environments to help the user get them aligned
|
||||
const nameAutocomplete = useMemo<GenericCompletionConfig>(() => {
|
||||
const options: GenericCompletionOption[] = [];
|
||||
if (isBaseEnvironment(selectedEnvironment)) {
|
||||
if (isBaseEnvironment(environment)) {
|
||||
return { options };
|
||||
}
|
||||
|
||||
@@ -59,8 +59,10 @@ export function EnvironmentEditor({
|
||||
const containingEnvs = allEnvironments.filter((e) =>
|
||||
e.variables.some((v) => v.name === name),
|
||||
);
|
||||
const isAlreadyInActive = containingEnvs.find((e) => e.id === selectedEnvironment.id);
|
||||
if (isAlreadyInActive) continue;
|
||||
const isAlreadyInActive = containingEnvs.find((e) => e.id === environment.id);
|
||||
if (isAlreadyInActive) {
|
||||
continue;
|
||||
}
|
||||
options.push({
|
||||
label: name,
|
||||
type: 'constant',
|
||||
@@ -68,7 +70,7 @@ export function EnvironmentEditor({
|
||||
});
|
||||
}
|
||||
return { options };
|
||||
}, [selectedEnvironment, allEnvironments]);
|
||||
}, [environment, allEnvironments]);
|
||||
|
||||
const validateName = useCallback((name: string) => {
|
||||
// Empty just means the variable doesn't have a name yet and is unusable
|
||||
@@ -79,10 +81,8 @@ export function EnvironmentEditor({
|
||||
const valueType = !isEncryptionEnabled && valueVisibility.value ? 'text' : 'password';
|
||||
const allVariableAreEncrypted = useMemo(
|
||||
() =>
|
||||
selectedEnvironment.variables.every(
|
||||
(v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure',
|
||||
),
|
||||
[selectedEnvironment.variables],
|
||||
environment.variables.every((v) => v.value === '' || analyzeTemplate(v.value) !== 'insecure'),
|
||||
[environment.variables],
|
||||
);
|
||||
|
||||
const encryptEnvironment = (environment: Environment) => {
|
||||
@@ -100,11 +100,11 @@ export function EnvironmentEditor({
|
||||
return (
|
||||
<VStack space={4} className={className}>
|
||||
<Heading className="w-full flex items-center gap-0.5">
|
||||
<EnvironmentColorIndicator clickToEdit environment={selectedEnvironment ?? null} />
|
||||
{!hideName && <div className="mr-2">{selectedEnvironment?.name}</div>}
|
||||
<EnvironmentColorIndicator clickToEdit environment={environment ?? null} />
|
||||
{!hideName && <div className="mr-2">{environment?.name}</div>}
|
||||
{isEncryptionEnabled ? (
|
||||
!allVariableAreEncrypted ? (
|
||||
<BadgeButton color="notice" onClick={() => encryptEnvironment(selectedEnvironment)}>
|
||||
<BadgeButton color="notice" onClick={() => encryptEnvironment(environment)}>
|
||||
Encrypt All Variables
|
||||
</BadgeButton>
|
||||
) : (
|
||||
@@ -121,22 +121,22 @@ export function EnvironmentEditor({
|
||||
color="secondary"
|
||||
rightSlot={<EnvironmentSharableTooltip />}
|
||||
onClick={async () => {
|
||||
await patchModel(selectedEnvironment, { public: !selectedEnvironment.public });
|
||||
await patchModel(environment, { public: !environment.public });
|
||||
}}
|
||||
>
|
||||
{selectedEnvironment.public ? 'Sharable' : 'Private'}
|
||||
{environment.public ? 'Sharable' : 'Private'}
|
||||
</BadgeButton>
|
||||
</Heading>
|
||||
{selectedEnvironment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
||||
{environment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
||||
<DismissibleBanner
|
||||
id={`warn-unencrypted-${selectedEnvironment.id}`}
|
||||
id={`warn-unencrypted-${environment.id}`}
|
||||
color="notice"
|
||||
className="mr-3"
|
||||
actions={[
|
||||
{
|
||||
label: 'Encrypt Variables',
|
||||
onClick: () => encryptEnvironment(selectedEnvironment),
|
||||
color: 'primary',
|
||||
onClick: () => encryptEnvironment(environment),
|
||||
color: 'success',
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -153,15 +153,11 @@ export function EnvironmentEditor({
|
||||
valueType={valueType}
|
||||
valueAutocompleteVariables
|
||||
valueAutocompleteFunctions
|
||||
forceUpdateKey={`${selectedEnvironment.id}::${forceUpdateKey}`}
|
||||
pairs={selectedEnvironment.variables}
|
||||
forceUpdateKey={`${environment.id}::${forceUpdateKey}`}
|
||||
pairs={environment.variables}
|
||||
onChange={handleChange}
|
||||
stateKey={`environment.${selectedEnvironment.id}`}
|
||||
forcedEnvironmentId={
|
||||
// Editing the base environment should resolve variables using the active environment.
|
||||
// Editing a sub environment should resolve variables as if it's the active environment
|
||||
isBaseEnvironment(selectedEnvironment) ? undefined : selectedEnvironment.id
|
||||
}
|
||||
stateKey={`environment.${environment.id}`}
|
||||
forcedEnvironmentId={environment.id}
|
||||
/>
|
||||
</div>
|
||||
</VStack>
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { activeEnvironmentIdAtom } from '../../../hooks/useActiveEnvironment';
|
||||
import type { WrappedEnvironmentVariable } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||
@@ -138,9 +137,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
|
||||
const activeEnvironmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
const environmentId = forcedEnvironmentId ?? activeEnvironmentId ?? null;
|
||||
const allEnvironmentVariables = useEnvironmentVariables(environmentId);
|
||||
const allEnvironmentVariables = useEnvironmentVariables(forcedEnvironmentId ?? null);
|
||||
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
|
||||
const useTemplating = !!(autocompleteFunctions || autocompleteVariables || autocomplete);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { EditorView } from '@codemirror/view';
|
||||
import type { Color } from '@yaakapp/api';
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import type { Environment, EnvironmentVariable } from '@yaakapp-internal/models';
|
||||
import { foldersAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { isFolderEnvironment } from '../lib/model_util';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { useActiveRequest } from './useActiveRequest';
|
||||
import { useEnvironmentsBreakdown } from './useEnvironmentsBreakdown';
|
||||
import { useParentFolders } from './useParentFolders';
|
||||
|
||||
export function useEnvironmentVariables(environmentId: string | null) {
|
||||
const { baseEnvironment, folderEnvironments, subEnvironments } = useEnvironmentsBreakdown();
|
||||
const activeEnvironment = subEnvironments.find((e) => e.id === environmentId) ?? null;
|
||||
export function useEnvironmentVariables(targetEnvironmentId: string | null) {
|
||||
const { baseEnvironment, folderEnvironments, allEnvironments } = useEnvironmentsBreakdown();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const targetEnvironment = allEnvironments.find((e) => e.id === targetEnvironmentId) ?? null;
|
||||
const activeRequest = useActiveRequest();
|
||||
const parentFolders = useParentFolders(activeRequest);
|
||||
const folders = useAtomValue(foldersAtom);
|
||||
const activeFolder = folders.find((f) => f.id === targetEnvironment?.parentId) ?? null;
|
||||
const parentFolders = useParentFolders(activeFolder ?? activeRequest);
|
||||
|
||||
return useMemo(() => {
|
||||
const varMap: Record<string, WrappedEnvironmentVariable> = {};
|
||||
@@ -18,9 +24,15 @@ export function useEnvironmentVariables(environmentId: string | null) {
|
||||
wrapVariables(folderEnvironments.find((fe) => fe.parentId === f.id) ?? null),
|
||||
);
|
||||
|
||||
// Folder environments also can auto-complete from the active environment
|
||||
const activeEnvironmentVariables =
|
||||
targetEnvironment == null || isFolderEnvironment(targetEnvironment)
|
||||
? wrapVariables(activeEnvironment)
|
||||
: [];
|
||||
|
||||
const allVariables = [
|
||||
...folderVariables,
|
||||
...wrapVariables(activeEnvironment),
|
||||
...activeEnvironmentVariables,
|
||||
...wrapVariables(baseEnvironment),
|
||||
];
|
||||
|
||||
@@ -32,7 +44,7 @@ export function useEnvironmentVariables(environmentId: string | null) {
|
||||
}
|
||||
|
||||
return Object.values(varMap);
|
||||
}, [activeEnvironment, baseEnvironment, folderEnvironments, parentFolders]);
|
||||
}, [activeEnvironment, baseEnvironment, folderEnvironments, parentFolders, targetEnvironment]);
|
||||
}
|
||||
|
||||
export interface WrappedEnvironmentVariable {
|
||||
|
||||
@@ -5,9 +5,9 @@ import { useMemo } from 'react';
|
||||
export function useEnvironmentsBreakdown() {
|
||||
const allEnvironments = useAtomValue(environmentsAtom);
|
||||
return useMemo(() => {
|
||||
const baseEnvironments = allEnvironments.filter((e) => e.parentId == null) ?? [];
|
||||
const baseEnvironments = allEnvironments.filter((e) => e.parentModel == 'workspace') ?? [];
|
||||
const subEnvironments =
|
||||
allEnvironments.filter((e) => e.parentModel === 'environment' && e.parentId != null) ?? [];
|
||||
allEnvironments.filter((e) => e.parentModel === 'environment') ?? [];
|
||||
const folderEnvironments =
|
||||
allEnvironments.filter((e) => e.parentModel === 'folder' && e.parentId != null) ?? [];
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ function getParentFolders(
|
||||
): Folder[] {
|
||||
if (currentModel == null) return [];
|
||||
|
||||
const folder = currentModel.folderId ? folders.find((f) => f.id === currentModel.folderId) : null;
|
||||
if (folder == null) {
|
||||
const parentFolder = currentModel.folderId ? folders.find((f) => f.id === currentModel.folderId) : null;
|
||||
if (parentFolder == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [folder, ...getParentFolders(folders, folder)];
|
||||
return [parentFolder, ...getParentFolders(folders, parentFolder)];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { InternalEvent } from '@yaakapp-internal/plugins';
|
||||
import type { ShowToastRequest } from '@yaakapp/api';
|
||||
import type { InternalEvent, ShowToastRequest } from '@yaakapp-internal/plugins';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { Button } from '../components/core/Button';
|
||||
|
||||
|
||||
@@ -51,3 +51,11 @@ export function getCharsetFromContentType(headers: HttpResponseHeader[]): string
|
||||
export function isBaseEnvironment(environment: Environment): boolean {
|
||||
return environment.parentId == null;
|
||||
}
|
||||
|
||||
export function isSubEnvironment(environment: Environment): boolean {
|
||||
return environment.parentModel == 'environment';
|
||||
}
|
||||
|
||||
export function isFolderEnvironment(environment: Environment): boolean {
|
||||
return environment.parentModel == 'folder';
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const yaakDark = {
|
||||
base: {
|
||||
surface: 'hsl(244,23%,14%)',
|
||||
surfaceHighlight: 'hsl(244,23%,20%)',
|
||||
text: 'hsl(245,23%,84%)',
|
||||
text: 'hsl(245,23%,85%)',
|
||||
textSubtle: 'hsl(245,18%,58%)',
|
||||
textSubtlest: 'hsl(245,18%,45%)',
|
||||
border: 'hsl(244,23%,25%)',
|
||||
@@ -67,11 +67,11 @@ const yaakDark = {
|
||||
},
|
||||
responsePane: {
|
||||
surface: 'hsl(243,23%,16%)',
|
||||
border: 'hsl(246,23%,22.72%)',
|
||||
border: 'hsl(246,23%,23%)',
|
||||
},
|
||||
appHeader: {
|
||||
surface: 'hsl(244,23%,12%)',
|
||||
border: 'hsl(244,23%,20.8%)',
|
||||
border: 'hsl(244,23%,21%)',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -83,22 +83,22 @@ const yaakLight = {
|
||||
base: {
|
||||
surface: 'hsl(0,0%,100%)',
|
||||
surfaceHighlight: 'hsl(218,24%,87%)',
|
||||
text: 'hsl(217,24%,15%)',
|
||||
text: 'hsl(217,24%,10%)',
|
||||
textSubtle: 'hsl(217,24%,40%)',
|
||||
textSubtlest: 'hsl(217,24%,58%)',
|
||||
border: 'hsl(217,22%,93%)',
|
||||
primary: 'hsl(266,100%,70%)',
|
||||
secondary: 'hsl(220,24%,59%)',
|
||||
info: 'hsl(206,100%,48%)',
|
||||
success: 'hsl(155,95%,33%)',
|
||||
notice: 'hsl(45,100%,41%)',
|
||||
warning: 'hsl(30,100%,43%)',
|
||||
danger: 'hsl(335,75%,57%)',
|
||||
border: 'hsl(217,22%,90%)',
|
||||
primary: 'hsl(266,100%,60%)',
|
||||
secondary: 'hsl(220,24%,50%)',
|
||||
info: 'hsl(206,100%,40%)',
|
||||
success: 'hsl(139,66%,34%)',
|
||||
notice: 'hsl(45,100%,34%)',
|
||||
warning: 'hsl(30,100%,36%)',
|
||||
danger: 'hsl(335,75%,48%)',
|
||||
},
|
||||
components: {
|
||||
sidebar: {
|
||||
surface: 'hsl(220,20%,97%)',
|
||||
border: 'hsl(217,22%,93%)',
|
||||
surface: 'hsl(220,20%,98%)',
|
||||
border: 'hsl(217,22%,88%)',
|
||||
surfaceHighlight: 'hsl(217,25%,90%)',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite dev --force",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext .ts,.tsx"
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "^6.8.1",
|
||||
|
||||
Reference in New Issue
Block a user