diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ff77212520..00b8b9110a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -28,7 +28,7 @@ members = [ "./parsers/windmill-parser-py", "./parsers/windmill-parser-py-imports", "./parsers/windmill-sql-datatype-parser-wasm", - "./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu", + "./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu" ] [workspace.package] diff --git a/backend/parsers/windmill-parser-csharp/src/lib.rs b/backend/parsers/windmill-parser-csharp/src/lib.rs index 0a74273a6c..53f6eb89dd 100644 --- a/backend/parsers/windmill-parser-csharp/src/lib.rs +++ b/backend/parsers/windmill-parser-csharp/src/lib.rs @@ -7,7 +7,7 @@ use anyhow::anyhow; use tree_sitter::Node; use windmill_parser::Arg; use windmill_parser::MainArgSignature; -use windmill_parser::Typ; +use windmill_parser::{ObjectType, Typ}; #[derive(Debug)] pub struct CsharpMainSigMeta { @@ -112,7 +112,7 @@ fn find_typ<'a>(typ_node: Node<'a>, code: &str) -> anyhow::Result { Ok("double") | Ok("System.Double") => Ok(Typ::Float), Ok("bool") | Ok("System.Boolean") => Ok(Typ::Bool), Ok("decimal") | Ok("System.Decimal") => Ok(Typ::Float), - Ok("object") => Ok(Typ::Object(vec![])), // TODO: Complete the object type + Ok("object") => Ok(Typ::Object(ObjectType::new(None, Some(vec![])))), // TODO: Complete the object type Ok(s) => Err(anyhow!("Unknown type `{s}`")), Err(e) => Err(anyhow!("Error getting type name: {}", e)), } diff --git a/backend/parsers/windmill-parser-go/src/lib.rs b/backend/parsers/windmill-parser-go/src/lib.rs index 218a6dc3ec..5df0d4e58e 100644 --- a/backend/parsers/windmill-parser-go/src/lib.rs +++ b/backend/parsers/windmill-parser-go/src/lib.rs @@ -7,7 +7,7 @@ use gosyn::{ use itertools::Itertools; use regex::Regex; -use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ}; lazy_static::lazy_static! { pub static ref REQUIRE_PARSE: Regex = Regex::new(r"//require (.*)\n").unwrap(); @@ -142,13 +142,13 @@ fn parse_go_typ(typ: &Expression) -> (Option, Typ) { "struct {{ {} }}", otyps.iter().join("; ").to_string() )), - Typ::Object(typs), + Typ::Object(ObjectType::new(None, Some(typs))), ) } - Expression::TypeInterface(_) => (Some("interface{}".to_string()), Typ::Object(vec![])), + Expression::TypeInterface(_) => (Some("interface{}".to_string()), Typ::Object(ObjectType::new(None, Some(vec![])))), Expression::TypeMap(_) => ( Some("map[string]interface{}".to_string()), - Typ::Object(vec![]), + Typ::Object(ObjectType::new(None, Some(vec![]))), ), _ => (None, Typ::Unknown), } @@ -218,10 +218,10 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam Arg { otyp: Some("struct { Name string `json:\"name\"` }".to_string()), name: "o".to_string(), - typ: Typ::Object(vec![ObjectProperty { + typ: Typ::Object(ObjectType::new(None, Some(vec![ObjectProperty { key: "name".to_string(), typ: Box::new(Typ::Str(None)) - },]), + },]))), default: None, has_default: false, oidx: None @@ -229,7 +229,7 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam Arg { otyp: Some("interface{}".to_string()), name: "n".to_string(), - typ: Typ::Object(vec![]), + typ: Typ::Object(ObjectType::new(None, Some(vec![]))), default: None, has_default: false, oidx: None @@ -237,7 +237,7 @@ func main(x int, y string, z bool, l []string, o struct { Name string `json:"nam Arg { otyp: Some("map[string]interface{}".to_string()), name: "m".to_string(), - typ: Typ::Object(vec![]), + typ: Typ::Object(ObjectType::new(None, Some(vec![]))), default: None, has_default: false, oidx: None diff --git a/backend/parsers/windmill-parser-graphql/src/lib.rs b/backend/parsers/windmill-parser-graphql/src/lib.rs index a900c3244d..02d3e9a891 100644 --- a/backend/parsers/windmill-parser-graphql/src/lib.rs +++ b/backend/parsers/windmill-parser-graphql/src/lib.rs @@ -9,7 +9,7 @@ use regex_lite::Regex; use serde_json::json; -use windmill_parser::{Arg, MainArgSignature, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ}; pub fn parse_graphql_sig(code: &str) -> anyhow::Result { let parsed = parse_graphql_file(&code)?; @@ -75,7 +75,7 @@ pub fn parse_graphql_typ(typ: &str) -> Typ { "Int" => Typ::Int, "Boolean" => Typ::Bool, "Float" => Typ::Float, - _ => Typ::Object(vec![]), + _ => Typ::Object(ObjectType::new(None, Some(vec![]))), } } diff --git a/backend/parsers/windmill-parser-java/src/lib.rs b/backend/parsers/windmill-parser-java/src/lib.rs index 93aa348cec..559462c902 100644 --- a/backend/parsers/windmill-parser-java/src/lib.rs +++ b/backend/parsers/windmill-parser-java/src/lib.rs @@ -9,7 +9,7 @@ use serde_json::Value; use tree_sitter::Node; use windmill_parser::Arg; use windmill_parser::MainArgSignature; -use windmill_parser::Typ; +use windmill_parser::{ObjectType, Typ}; #[derive(Debug)] pub struct JavaMainSigMeta { @@ -101,7 +101,7 @@ fn find_typ<'a>(typ_node: Node<'a>, code: &str) -> anyhow::Result<(Typ, Option (Typ::Float, null), Ok("Boolean") => (Typ::Bool, null), Ok("Character") => (Typ::Str(None), null), - Ok("Object") => (Typ::Object(vec![]),null), // TODO: Complete the object type + Ok("Object") => (Typ::Object(ObjectType::new(None, Some(vec![]))),null), // TODO: Complete the object type Ok(s) => bail!("Unknown type `{s}`"), Err(e) => bail!("Error getting type name: {}", e), } @@ -394,7 +394,7 @@ class Main { Arg { name: "i".into(), otyp: Some("Object".into()), - typ: Typ::Object(vec![]), + typ: Typ::Object(ObjectType::new(None, Some(vec![]))), default: Some(json!(null)), has_default: true, oidx: None @@ -427,7 +427,7 @@ class Main { Arg { name: "b".into(), otyp: Some("Object[]".into()), - typ: Typ::List(Box::new(Typ::Object(vec![]))), + typ: Typ::List(Box::new(Typ::Object(ObjectType::new(None, Some(vec![]))))), default: Some(json!(null)), has_default: true, oidx: None diff --git a/backend/parsers/windmill-parser-nu/src/lib.rs b/backend/parsers/windmill-parser-nu/src/lib.rs index ddd049f609..76f124efad 100644 --- a/backend/parsers/windmill-parser-nu/src/lib.rs +++ b/backend/parsers/windmill-parser-nu/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, bail}; use nu_parser::lex; use serde_json::{json, Value}; -use windmill_parser::{Arg, MainArgSignature, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ}; pub fn parse_nu_signature(code: &str) -> anyhow::Result { let (tokens, ..) = lex(code.as_bytes(), 0, &[], &[], true); @@ -162,8 +162,8 @@ pub fn parse_nu_signature(code: &str) -> anyhow::Result { "int" => Typ::Int, "float" => Typ::Float, "number" => Typ::Float, - "record" => Typ::Object(vec![]), - "table" => Typ::List(Box::new(Typ::Object(vec![]))), + "record" => Typ::Object(ObjectType::new(None, Some(vec![]))), + "table" => Typ::List(Box::new(Typ::Object(ObjectType::new(None, Some(vec![]))))), "nothing" => Typ::Unknown, // TODO: needs additional work on literal parsing // "binary" => Typ::Bytes, diff --git a/backend/parsers/windmill-parser-nu/tests/tests.rs b/backend/parsers/windmill-parser-nu/tests/tests.rs index 13c54930d6..315041cb55 100644 --- a/backend/parsers/windmill-parser-nu/tests/tests.rs +++ b/backend/parsers/windmill-parser-nu/tests/tests.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod test { use serde_json::json; - use windmill_parser::{Arg, MainArgSignature, Typ}; + use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ}; use windmill_parser_nu::parse_nu_signature; #[test] @@ -200,7 +200,7 @@ mod test { Arg { name: "a7".into(), otyp: None, - typ: Typ::Object(vec![]), + typ: Typ::Object(ObjectType::new(None, Some(vec![]))), default: None, has_default: false, oidx: None @@ -216,7 +216,7 @@ mod test { Arg { name: "a9".into(), otyp: None, - typ: Typ::List(Box::new(Typ::Object(vec![]))), + typ: Typ::List(Box::new(Typ::Object(ObjectType::new(None, Some(vec![]))))), default: None, has_default: false, oidx: None diff --git a/backend/parsers/windmill-parser-php/src/lib.rs b/backend/parsers/windmill-parser-php/src/lib.rs index 4c239c21c9..1ac2b9a63a 100644 --- a/backend/parsers/windmill-parser-php/src/lib.rs +++ b/backend/parsers/windmill-parser-php/src/lib.rs @@ -1,5 +1,5 @@ use serde_json::Value; -use windmill_parser::{to_snake_case, Arg, MainArgSignature, Typ}; +use windmill_parser::{to_snake_case, Arg, MainArgSignature, ObjectType, Typ}; use php_parser_rs::parser::{ self, @@ -18,7 +18,7 @@ fn parse_php_type(e: Type) -> Typ { Type::Integer(_) => Typ::Int, Type::String(_) => Typ::Str(None), Type::Array(_) => Typ::List(Box::new(Typ::Str(None))), - Type::Object(_) => Typ::Object(vec![]), + Type::Object(_) => Typ::Object(ObjectType::new(None, Some(vec![]))), Type::Named(_, name) => Typ::Resource(to_snake_case(name.to_string().as_ref())), _ => Typ::Unknown, } diff --git a/backend/parsers/windmill-parser-py/src/lib.rs b/backend/parsers/windmill-parser-py/src/lib.rs index 9b5736a18d..52983872c6 100644 --- a/backend/parsers/windmill-parser-py/src/lib.rs +++ b/backend/parsers/windmill-parser-py/src/lib.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use itertools::Itertools; use serde_json::json; -use windmill_parser::{json_to_typ, Arg, MainArgSignature, Typ}; +use windmill_parser::{json_to_typ, Arg, MainArgSignature, ObjectType, Typ}; use rustpython_parser::{ ast::{ @@ -226,7 +226,7 @@ fn parse_typ(id: &str) -> Typ { "float" => Typ::Float, "int" => Typ::Int, "bool" => Typ::Bool, - "dict" => Typ::Object(vec![]), + "dict" => Typ::Object(ObjectType::new(None, Some(vec![]))), "list" => Typ::List(Box::new(Typ::Unknown)), "bytes" => Typ::Bytes, "datetime" => Typ::Datetime, diff --git a/backend/parsers/windmill-parser-sql/src/lib.rs b/backend/parsers/windmill-parser-sql/src/lib.rs index 6651dc1dd5..5856fbc4fd 100644 --- a/backend/parsers/windmill-parser-sql/src/lib.rs +++ b/backend/parsers/windmill-parser-sql/src/lib.rs @@ -14,7 +14,7 @@ use std::{ iter::Peekable, str::CharIndices, }; -pub use windmill_parser::{Arg, MainArgSignature, Typ}; +pub use windmill_parser::{Arg, MainArgSignature, ObjectType, Typ}; pub const SANITIZED_ENUM_STR: &str = "__sanitized_enum__"; pub const SANITIZED_RAW_STRING_STR: &str = "__sanitized_raw_string__"; @@ -737,7 +737,7 @@ pub fn parse_pg_typ(typ: &str) -> Typ { "bigint" => Typ::Int, "bool" | "boolean" => Typ::Bool, "char" | "character" => Typ::Str(None), - "json" | "jsonb" => Typ::Object(vec![]), + "json" | "jsonb" => Typ::Object(ObjectType::new(None, Some(vec![]))), "smallint" | "int2" => Typ::Int, "smallserial" | "serial2" => Typ::Int, "serial" | "serial4" => Typ::Int, @@ -769,7 +769,7 @@ pub fn parse_bigquery_typ(typ: &str) -> Typ { match typ { "string" => Typ::Str(None), "bytes" => Typ::Bytes, - "json" => Typ::Object(vec![]), + "json" => Typ::Object(ObjectType::new(None, Some(vec![]))), "timestamp" | "date" | "time" | "datetime" => Typ::Datetime, "integer" | "int64" => Typ::Int, "float" | "float64" | "numeric" | "bignumeric" => Typ::Float, diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 2c0c52fc72..7f74cda756 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -7,19 +7,20 @@ */ // use deno_core::{serde_v8, v8, JsRuntime, RuntimeOptions}; use serde_json::Value; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use windmill_parser::{ - json_to_typ, to_snake_case, Arg, MainArgSignature, ObjectProperty, OneOfVariant, Typ, + json_to_typ, to_snake_case, Arg, MainArgSignature, ObjectProperty, ObjectType, OneOfVariant, + Typ, }; use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Span, Spanned}; use swc_ecma_ast::{ - ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, FnDecl, Ident, - IdentName, Lit, MemberExpr, MemberProp, ModuleDecl, ModuleItem, Number, ObjectLit, ObjectPat, - Param, Pat, Str, TsArrayType, TsEntityName, TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, - TsOptionalType, TsParenthesizedType, TsPropertySignature, TsType, TsTypeAnn, TsTypeElement, - TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType, + ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, Ident, IdentName, Lit, + MemberExpr, MemberProp, ModuleDecl, ModuleItem, Number, ObjectLit, ObjectPat, Param, Pat, Stmt, + Str, TsArrayType, TsEntityName, TsInterfaceDecl, TsKeywordType, TsKeywordTypeKind, TsLit, + TsLitType, TsOptionalType, TsParenthesizedType, TsPropertySignature, TsType, TsTypeAliasDecl, + TsTypeAnn, TsTypeElement, TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType, }; use swc_ecma_parser::{lexer::Lexer, EsSyntax, Parser, StringInput, Syntax, TsSyntax}; @@ -205,6 +206,11 @@ pub fn parse_expr_for_ids(code: &str) -> anyhow::Result> { Ok(visitor.idents.into_iter().collect()) } +#[derive(Debug)] +pub enum TypeDecl { + Interface(TsInterfaceDecl), + Alias(TsTypeAliasDecl), +} pub mod asset_parser; pub use asset_parser::parse_assets; @@ -214,7 +220,7 @@ pub fn parse_deno_signature( code: &str, skip_dflt: bool, skip_params: bool, - main_override: Option, + entrypoint_override: Option, ) -> anyhow::Result { let cm: Lrc = Default::default(); let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into()); @@ -234,6 +240,9 @@ pub fn parse_deno_signature( err_s += &e.into_kind().msg().to_string(); } + let mut has_preprocessor = false; + let mut entrypoint_params = None; + let ast = parser .parse_module() .map_err(|e| { @@ -241,35 +250,71 @@ pub fn parse_deno_signature( })? .body; - let has_preprocessor = ast.iter().any(|x| match x { - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - decl: Decl::Fn(FnDecl { ident: Ident { sym, .. }, .. }), - .. - })) => &sym.to_string() == "preprocessor", - _ => false, - }); + let entrypoint_function = entrypoint_override.as_deref().unwrap_or("main"); - let main_name = main_override.unwrap_or("main".to_string()); - let params = ast.into_iter().find_map(|x| match x { - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - decl: Decl::Fn(FnDecl { ident: Ident { sym, .. }, function, .. }), - .. - })) if &sym.to_string() == &main_name => Some(function.params), - _ => None, - }); + let mut symbol_table: HashMap = HashMap::new(); + + for item in ast { + if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. })) + | ModuleItem::Stmt(Stmt::Decl(decl)) = item + { + match decl { + Decl::TsInterface(mut iface) => match symbol_table.get_mut(iface.id.sym.as_str()) { + Some(TypeDecl::Interface(interface)) => { + interface.body.body.append(&mut iface.body.body); + } + None => { + symbol_table.insert( + to_snake_case(iface.id.sym.as_str()), + TypeDecl::Interface(*iface), + ); + } + _ => {} + }, + Decl::TsTypeAlias(alias) => { + symbol_table.insert( + to_snake_case(alias.id.sym.as_str()), + TypeDecl::Alias(*alias), + ); + } + Decl::Fn(fn_decl) => { + let name = fn_decl.ident.sym.to_string(); + if name == "preprocessor" { + has_preprocessor = true; + } + if name == entrypoint_function { + entrypoint_params = Some(fn_decl.function.params.clone()); + } + } + _ => {} + } + } + } let mut c: u16 = 0; - let no_main_func = params.is_none(); + + let no_main_func = entrypoint_params.is_none(); + let mut type_resolver = HashMap::new(); let r = MainArgSignature { star_args: false, star_kwargs: false, args: if skip_params { vec![] } else { - params - .map(|x| { - x.into_iter() - .map(|x| parse_param(x, &cm, skip_dflt, &mut c)) + entrypoint_params + .map(|param| { + param + .into_iter() + .map(|param| { + parse_param( + &symbol_table, + &mut type_resolver, + param, + &cm, + skip_dflt, + &mut c, + ) + }) .collect::>>() }) .transpose()? @@ -282,14 +327,16 @@ pub fn parse_deno_signature( } fn parse_param( - x: Param, + symbol_table: &HashMap, + type_resolver: &mut HashMap, + param: Param, cm: &Lrc, skip_dflt: bool, counter: &mut u16, ) -> anyhow::Result { - let r = match x.pat { + let r = match param.pat { Pat::Ident(ident) => { - let (name, typ, nullable) = binding_ident_to_arg(&ident); + let (name, typ, nullable) = binding_ident_to_arg(symbol_table, type_resolver, &ident); Ok(Arg { otyp: None, name, @@ -302,9 +349,9 @@ fn parse_param( // Pat::Object(ObjectPat { ... }) = todo!() Pat::Assign(AssignPat { left, right, .. }) => { let (name, mut typ, _nullable) = match *left { - Pat::Ident(ident) => binding_ident_to_arg(&ident), + Pat::Ident(ident) => binding_ident_to_arg(symbol_table, type_resolver, &ident), Pat::Object(ObjectPat { type_ann, .. }) => { - let (typ, nullable) = eval_type_ann(&type_ann); + let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann); *counter += 1; let name = format!("anon{}", counter); (name, typ, nullable) @@ -346,16 +393,16 @@ fn parse_param( Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true, oidx: None }) } Pat::Object(ObjectPat { type_ann, .. }) => { - let (typ, nullable) = eval_type_ann(&type_ann); + let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, &type_ann); *counter += 1; let name = format!("anon{}", counter); Ok(Arg { otyp: None, name, typ, default: None, has_default: nullable, oidx: None }) } _ => Err(anyhow::anyhow!( "parameter syntax unsupported: `{}`: {:#?}", - cm.span_to_snippet(x.span()) - .unwrap_or_else(|_| cm.span_to_string(x.span())), - x.pat + cm.span_to_snippet(param.span()) + .unwrap_or_else(|_| cm.span_to_string(param.span())), + param.pat )), }; r @@ -374,14 +421,22 @@ fn eval_span(span: Span, cm: &Lrc) -> Option { } } -fn eval_type_ann(type_ann: &Option>) -> (Typ, bool) { +fn eval_type_ann( + symbol_table: &HashMap, + type_resolver: &mut HashMap, + type_ann: &Option>, +) -> (Typ, bool) { return type_ann .as_ref() - .map(|x| tstype_to_typ(&*x.type_ann)) + .map(|x| tstype_to_typ(symbol_table, type_resolver, &*x.type_ann, true)) .unwrap_or((Typ::Unknown, false)); } -fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String, Typ, bool) { - let (typ, nullable) = eval_type_ann(type_ann); +fn binding_ident_to_arg( + symbol_table: &HashMap, + type_resolver: &mut HashMap, + BindingIdent { id, type_ann }: &BindingIdent, +) -> (String, Typ, bool) { + let (typ, nullable) = eval_type_ann(symbol_table, type_resolver, type_ann); (id.sym.to_string(), typ, nullable) } @@ -411,11 +466,158 @@ pub fn remove_pinned_imports(code: &str) -> anyhow::Result { Ok(content) } -fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { +fn resolve_type_ref(type_resolver: &HashMap, typ: &mut Typ) { + let mut visited = std::collections::HashSet::new(); + resolve_type_ref_with_visited(type_resolver, typ, &mut visited); +} + +fn resolve_type_ref_with_visited( + type_resolver: &HashMap, + typ: &mut Typ, + visited: &mut std::collections::HashSet, +) { + match typ { + Typ::Object(ObjectType { props: Some(obj), .. }) => { + for property in obj.iter_mut() { + resolve_type_ref_with_visited(type_resolver, &mut property.typ, visited); + } + } + Typ::List(list) => resolve_type_ref_with_visited(type_resolver, list, visited), + Typ::Object(ObjectType { name: Some(name), props: None }) => { + if visited.contains(name) { + return; + } + let maybe_resolved_type = type_resolver + .get(name) + .map(|rs_typ| { + let mut typ = rs_typ.0.clone(); + visited.insert(name.clone()); + resolve_type_ref_with_visited(type_resolver, &mut typ, visited); + visited.remove(name); + typ + }) + .unwrap_or(Typ::Object(ObjectType::new(Some(name.to_owned()), None))); + + *typ = maybe_resolved_type; + } + _ => {} + } +} + +fn resolve_interface( + iface: &TsInterfaceDecl, + symbol_table: &HashMap, + type_resolver: &mut HashMap, +) -> Vec { + let mut properties = vec![]; + + for ext in &iface.extends { + // If the current interface extends other interfaces, + // retrieve their properties first and add them to the current interface's object definition. + if let Expr::Ident(Ident { sym, .. }) = &*ext.expr { + if let Some(TypeDecl::Interface(parent_iface)) = + symbol_table.get(&to_snake_case(sym.as_str())) + { + properties.extend(resolve_interface(parent_iface, symbol_table, type_resolver)); + } + } + } + + for member in &iface.body.body { + if let TsTypeElement::TsPropertySignature(sig) = member { + if let Expr::Ident(Ident { sym, .. }) = &*sig.key { + let typ = sig + .type_ann + .as_ref() + .map(|ta| { + Box::new(tstype_to_typ(symbol_table, type_resolver, &ta.type_ann, false).0) + }) + .unwrap_or(Box::new(Typ::Unknown)); + + properties.push(ObjectProperty { key: sym.to_string(), typ }); + } + } + } + + properties +} + +fn resolve_type_alias( + alias: &TsTypeAliasDecl, + symbol_table: &HashMap, + type_resolver: &mut HashMap, + top_level_call: bool, +) -> (Typ, bool) { + tstype_to_typ(symbol_table, type_resolver, &alias.type_ann, top_level_call) +} + +fn resolve_ts_interface_and_type_alias( + type_name: &str, + symbol_table: &HashMap, + type_resolver: &mut HashMap, + top_level_call: bool, +) -> Option<(Typ, bool)> { + let Some(type_declaration) = symbol_table.get(type_name) else { + return None; + }; + + if let Some(resolved_type) = type_resolver.get_mut(type_name) { + return Some(resolved_type.to_owned()); + } + + type_resolver.insert( + type_name.to_owned(), + ( + Typ::Object(ObjectType::new(Some(type_name.to_owned()), None)), + false, + ), + ); + + let mut resolved_type = match type_declaration { + TypeDecl::Alias(alias) => { + resolve_type_alias(alias, symbol_table, type_resolver, top_level_call) + } + TypeDecl::Interface(iface) => ( + Typ::Object(ObjectType::new( + Some(to_snake_case(&type_name)), + Some(resolve_interface(iface, symbol_table, type_resolver)), + )), + false, + ), + }; + + if let Typ::Object(obj) = &mut resolved_type.0 { + if obj.name.is_none() { + obj.name = Some(type_name.to_owned()); + } + } + + type_resolver.insert(type_name.to_owned(), resolved_type.clone()); + + // `top_level_call` indicates whether the current invocation of the function + // is at the topmost level (e.g., the immediate parameters of the main function). + // When true: + // - Type references within object properties (e.g., nested interfaces) are recursively resolved + // up to a default depth to inline and fully materialize their structure. + if top_level_call { + resolve_type_ref(type_resolver, &mut resolved_type.0); + } + + Some(resolved_type) +} + +fn tstype_to_typ( + symbol_table: &HashMap, + type_resolver: &mut HashMap, + ts_type: &TsType, + top_level_call: bool, +) -> (Typ, bool) { match ts_type { TsType::TsKeywordType(t) => ( match t.kind { - TsKeywordTypeKind::TsObjectKeyword => Typ::Object(vec![]), + TsKeywordTypeKind::TsObjectKeyword => { + Typ::Object(ObjectType::new(None, Some(vec![]))) + } TsKeywordTypeKind::TsBooleanKeyword => Typ::Bool, TsKeywordTypeKind::TsBigIntKeyword => Typ::Int, TsKeywordTypeKind::TsNumberKeyword => Typ::Float, @@ -437,7 +639,17 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { key: sym.to_string(), typ: type_ann .as_ref() - .map(|typ| Box::new(tstype_to_typ(&*typ.type_ann).0)) + .map(|typ| { + Box::new( + tstype_to_typ( + symbol_table, + type_resolver, + &*typ.type_ann, + top_level_call, + ) + .0, + ) + }) .unwrap_or(Box::new(Typ::Unknown)), }), _ => None, @@ -445,21 +657,25 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { _ => None, }) .collect(); - (Typ::Object(properties), false) + (Typ::Object(ObjectType::new(None, Some(properties))), false) } TsType::TsParenthesizedType(TsParenthesizedType { type_ann, .. }) => { - tstype_to_typ(type_ann) + tstype_to_typ(symbol_table, type_resolver, type_ann, top_level_call) } // TODO: we can do better here and extract the inner type of array - TsType::TsArrayType(TsArrayType { elem_type, .. }) => { - (Typ::List(Box::new(tstype_to_typ(&**elem_type).0)), false) - } + TsType::TsArrayType(TsArrayType { elem_type, .. }) => ( + Typ::List(Box::new( + tstype_to_typ(symbol_table, type_resolver, &**elem_type, top_level_call).0, + )), + false, + ), TsType::TsLitType(TsLitType { lit: TsLit::Str(Str { value, .. }), .. }) => { (Typ::Str(Some(vec![value.to_string()])), false) } - TsType::TsOptionalType(TsOptionalType { type_ann, .. }) => { - (tstype_to_typ(type_ann).0, true) - } + TsType::TsOptionalType(TsOptionalType { type_ann, .. }) => ( + tstype_to_typ(symbol_table, type_resolver, type_ann, top_level_call).0, + true, + ), TsType::TsUnionOrIntersectionType(TsUnionOrIntersectionType::TsUnionType( TsUnionType { types, .. }, )) => { @@ -484,11 +700,18 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { } else { 0 }; - (tstype_to_typ(&types[other_p]).0, true) + ( + tstype_to_typ(symbol_table, type_resolver, &types[other_p], top_level_call).0, + true, + ) } else { if types.len() > 1 { - let one_of_values: Vec = - types.into_iter().map_while(parse_one_of_type).collect(); + let one_of_values: Vec = types + .into_iter() + .map_while(|t| { + parse_one_of_type(symbol_table, type_resolver, t, top_level_call) + }) + .collect(); if one_of_values.len() == types.len() { return (Typ::OneOf(one_of_values), false); @@ -527,6 +750,7 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { TsEntityName::Ident(Ident { sym, .. }) => sym, TsEntityName::TsQualifiedName(p) => &*p.right.sym, }; + match sym.to_string().as_str() { "Resource" => ( Typ::Resource( @@ -547,24 +771,69 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) { "Base64" => (Typ::Bytes, false), "Email" => (Typ::Email, false), "Sql" => (Typ::Sql, false), - x @ _ if x.starts_with("DynSelect_") => ( - Typ::DynSelect(x.strip_prefix("DynSelect_").unwrap().to_string()), + symbol @ _ if symbol.starts_with("DynSelect_") => ( + Typ::DynSelect(symbol.strip_prefix("DynSelect_").unwrap().to_string()), false, ), - x @ _ => (Typ::Resource(to_snake_case(x)), false), + symbol @ _ => { + let symbol = to_snake_case(symbol); + + resolve_ts_interface_and_type_alias( + &symbol, + symbol_table, + type_resolver, + top_level_call, + ) + .unwrap_or_else(|| (Typ::Resource(symbol), false)) + } } } _ => (Typ::Unknown, false), } } -fn parse_one_of_type(x: &Box) -> Option { +fn parse_one_of_type( + symbol_table: &HashMap, + type_resolver: &mut HashMap, + x: &Box, + top_level_call: bool, +) -> Option { match &**x { TsType::TsTypeLit(TsTypeLit { members, .. }) => { let label = one_of_label(members)?; - let properties = one_of_properties(members); + let properties = + one_of_properties(symbol_table, type_resolver, members, top_level_call); Some(OneOfVariant { label, properties }) } + TsType::TsTypeRef(TsTypeRef { type_name, .. }) => { + let label = type_name.as_ident()?.sym.to_string(); + match label.as_str() { + symbol + if ["Resource", "Date", "Base64", "Email", "Sql"] + .iter() + .any(|s| *s == symbol) + || symbol.starts_with("DynSelect_") => + { + return None + } + symbol @ _ => { + let Typ::Object(ObjectType { props: Some(properties), .. }) = + resolve_ts_interface_and_type_alias( + symbol, + symbol_table, + type_resolver, + top_level_call, + ) + .unwrap_or_else(|| (Typ::Resource(to_snake_case(symbol)), false)) + .0 + else { + return None; + }; + + Some(OneOfVariant { label, properties }) + } + } + } _ => None, } } @@ -596,7 +865,12 @@ fn one_of_label(members: &Vec) -> Option { }) } -fn one_of_properties(members: &Vec) -> Vec { +fn one_of_properties( + symbol_table: &HashMap, + type_resolver: &mut HashMap, + members: &Vec, + top_level_call: bool, +) -> Vec { members .iter() .filter_map(|x| { @@ -610,10 +884,12 @@ fn one_of_properties(members: &Vec) -> Vec { }; let typ = type_ann .as_ref() - .map(|typ| Box::new(tstype_to_typ(&*typ.type_ann).0)) - .unwrap_or(Box::new(Typ::Unknown)); + .map(|typ| { + tstype_to_typ(symbol_table, type_resolver, &*typ.type_ann, top_level_call).0 + }) + .unwrap_or(Typ::Unknown); - Some(ObjectProperty { key: sym.to_string(), typ }) + Some(ObjectProperty { key: sym.to_string(), typ: Box::new(typ) }) }) .collect() } diff --git a/backend/parsers/windmill-parser-wasm/build.nu b/backend/parsers/windmill-parser-wasm/build.nu index cb7423fb31..60fdead893 100755 --- a/backend/parsers/windmill-parser-wasm/build.nu +++ b/backend/parsers/windmill-parser-wasm/build.nu @@ -115,7 +115,7 @@ def main [ wasm-pack build ($profile) --target ($tar) --out-dir $env.OUT_DIR --features ($t.features) -Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort }, "tree-sitter" => { - $env.CFLAGS_wasm32_unknown_unknown = "-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" + $env.CFLAGS_wasm32_unknown_unknown = $"-I(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin" $env.RUSTFLAGS = "-Zwasm-c-abi=spec" wasm-pack build ($profile) --target ($tar) --out-dir $env.OUT_DIR --features $t.features }, @@ -123,9 +123,9 @@ def main [ } if ($cli) { - rm ($env.OUT_DIR)/.gitignore + rm $"($env.OUT_DIR)/.gitignore" } else { - let p = ($env.OUT_DIR)/package.json + let p = $"($env.OUT_DIR)/package.json" open $p | update name $"windmill-parser-wasm-($t.ident)" | save -f $p } } diff --git a/backend/parsers/windmill-parser-wasm/publish-pkgs.sh b/backend/parsers/windmill-parser-wasm/publish-pkgs.sh index 4180853b8b..e8215f8fa4 100755 --- a/backend/parsers/windmill-parser-wasm/publish-pkgs.sh +++ b/backend/parsers/windmill-parser-wasm/publish-pkgs.sh @@ -30,3 +30,6 @@ popd pushd "pkg-nu" && npm publish ${args} popd + +pushd "pkg-java" && npm publish ${args} +popd diff --git a/backend/parsers/windmill-parser-wasm/tests/wasm.rs b/backend/parsers/windmill-parser-wasm/tests/wasm.rs index 35845e179a..204db994d7 100644 --- a/backend/parsers/windmill-parser-wasm/tests/wasm.rs +++ b/backend/parsers/windmill-parser-wasm/tests/wasm.rs @@ -1,6 +1,6 @@ use serde_json::json; use wasm_bindgen_test::wasm_bindgen_test; -use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ}; use windmill_parser_bash::parse_powershell_sig; use windmill_parser_ts::{parse_deno_signature, parse_expr_for_ids, parse_expr_for_imports}; @@ -115,10 +115,10 @@ export function main(test1?: string, test2: string = \"burkina\", Arg { otyp: None, name: "min_object".to_string(), - typ: Typ::Object(vec![ + typ: Typ::Object(ObjectType::new(None, Some(vec![ ObjectProperty { key: "a".to_string(), typ: Box::new(Typ::Str(None)) }, ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Float) } - ]), + ]))), default: None, has_default: false, oidx: None @@ -210,10 +210,10 @@ export function main(test2 = \"burkina\", Arg { otyp: None, name: "min_object".to_string(), - typ: Typ::Object(vec![ + typ: Typ::Object(ObjectType::new(None, Some(vec![ ObjectProperty { key: "a".to_string(), typ: Box::new(Typ::Str(None)) }, ObjectProperty { key: "b".to_string(), typ: Box::new(Typ::Int) } - ]), + ]))), default: Some(json!({"a": "test", "b": 42})), has_default: true, oidx: None diff --git a/backend/parsers/windmill-parser-yaml/src/lib.rs b/backend/parsers/windmill-parser-yaml/src/lib.rs index 06adac1ee3..710d17c966 100644 --- a/backend/parsers/windmill-parser-yaml/src/lib.rs +++ b/backend/parsers/windmill-parser-yaml/src/lib.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::anyhow; use serde_json::json; -use windmill_parser::{Arg, MainArgSignature, ObjectProperty, Typ}; +use windmill_parser::{Arg, MainArgSignature, ObjectProperty, ObjectType, Typ}; use yaml_rust::{Yaml, YamlEmitter, YamlLoader}; pub fn parse_ansible_sig(inner_content: &str) -> anyhow::Result { @@ -147,9 +147,9 @@ fn parse_ansible_typ(arg: &Yaml) -> Typ { }) } } - Typ::Object(prop_vec) + Typ::Object(ObjectType::new(None, Some(prop_vec))) } else { - Typ::Object(vec![]) + Typ::Object(ObjectType::new(None, Some(vec![]))) } } "array" => { diff --git a/backend/parsers/windmill-parser/src/lib.rs b/backend/parsers/windmill-parser/src/lib.rs index 8a3ac91555..493222c3c3 100644 --- a/backend/parsers/windmill-parser/src/lib.rs +++ b/backend/parsers/windmill-parser/src/lib.rs @@ -28,6 +28,24 @@ pub struct ObjectProperty { pub typ: Box, } +impl ObjectProperty { + pub fn new(key: String, typ: Box) -> ObjectProperty { + ObjectProperty { key, typ } + } +} + +#[derive(Serialize, Clone, Debug, PartialEq)] +pub struct ObjectType { + pub name: Option, + pub props: Option>, +} + +impl ObjectType { + pub fn new(name: Option, props: Option>) -> ObjectType { + ObjectType { name, props } + } +} + #[derive(Serialize, Clone, Debug, PartialEq)] #[serde(rename_all(serialize = "lowercase"))] pub struct OneOfVariant { @@ -49,7 +67,7 @@ pub enum Typ { Email, Sql, DynSelect(String), - Object(Vec), + Object(ObjectType), OneOf(Vec), Unknown, } @@ -70,11 +88,14 @@ pub fn json_to_typ(js: &Value) -> Typ { Value::Number(n) if n.is_i64() => Typ::Int, Value::Number(_) => Typ::Float, Value::Bool(_) => Typ::Bool, - Value::Object(o) => Typ::Object( - o.iter() - .map(|(k, v)| ObjectProperty { key: k.to_string(), typ: Box::new(json_to_typ(v)) }) - .collect(), - ), + Value::Object(o) => Typ::Object(ObjectType::new( + None, + Some( + o.iter() + .map(|(k, v)| ObjectProperty { key: k.to_string(), typ: Box::new(json_to_typ(v)) }) + .collect(), + ), + )), Value::Array(a) => Typ::List(Box::new(a.first().map(json_to_typ).unwrap_or(Typ::Unknown))), _ => Typ::Unknown, } diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 07635f4f0d..ac8ccd4033 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -14641,36 +14641,41 @@ components: - type: object properties: object: - type: array - items: - type: object - properties: - key: - type: string - typ: - oneOf: - - type: string - enum: - [ - "float", - "int", - "bool", - "email", - "unknown", - "bytes", - "dict", - "datetime", - "sql", - ] - - type: object - properties: - str: {} - required: [str] - required: - - key - - typ + type: object + properties: + name: + type: string + props: + type: array + items: + type: object + properties: + key: + type: string + typ: + oneOf: + - type: string + enum: + [ + "float", + "int", + "bool", + "email", + "unknown", + "bytes", + "dict", + "datetime", + "sql", + ] + - type: object + properties: + str: {} + required: [str] + required: + - key + - typ required: - - object + - object - type: object properties: list: diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 666ed9ca62..d3addaf958 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -3079,7 +3079,7 @@ struct CancelJob { reason: Option, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] enum PreviewKind { Code, @@ -3090,7 +3090,7 @@ enum PreviewKind { ScriptHash, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct Preview { content: Option, kind: Option, diff --git a/backend/windmill-worker/src/schema.rs b/backend/windmill-worker/src/schema.rs index f5cce5cd90..f3ae7d9688 100644 --- a/backend/windmill-worker/src/schema.rs +++ b/backend/windmill-worker/src/schema.rs @@ -46,11 +46,13 @@ fn make_rules_for_arg_typ(typ: &Typ) -> Vec { Typ::Sql => { rules.push(SchemaValidationRule::IsString); } - Typ::Object(props) => { + Typ::Object(object_type) => { let mut obj_rules = vec![]; - for prop in props { - obj_rules.push((prop.key.to_string(), make_rules_for_arg_typ(&prop.typ))); + if let Some(props) = &object_type.props { + for prop in props { + obj_rules.push((prop.key.to_string(), make_rules_for_arg_typ(&prop.typ))); + } } rules.push(SchemaValidationRule::IsObject(obj_rules)) diff --git a/cli/metadata.ts b/cli/metadata.ts index 8232057b0b..571262124b 100644 --- a/cli/metadata.ts +++ b/cli/metadata.ts @@ -14,7 +14,11 @@ import { } from "./bootstrap/script_bootstrap.ts"; import { Workspace } from "./workspace.ts"; import { SchemaProperty } from "./bootstrap/common.ts"; -import { languagesWithRawReqsSupport, LanguageWithRawReqsSupport, ScriptLanguage } from "./script_common.ts"; +import { + languagesWithRawReqsSupport, + LanguageWithRawReqsSupport, + ScriptLanguage, +} from "./script_common.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; import { GlobalDeps, exts, findGlobalDeps } from "./script.ts"; import { @@ -37,12 +41,12 @@ export class LockfileGenerationError extends Error { } } -export async function generateAllMetadata() { } +export async function generateAllMetadata() {} function findClosestRawReqs( lang: LanguageWithRawReqsSupport | undefined, remotePath: string, - globalDeps: GlobalDeps, + globalDeps: GlobalDeps ): string | undefined { let bestCandidate: { k: string; v: string } | undefined = undefined; if (lang) { @@ -74,11 +78,14 @@ async function generateFlowHash( // Get language name from path const lang = inferContentTypeFromFilePath(f.path, defaultTs); // Get lock for that language - [, reqs] = Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? []; + [, reqs] = + Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? []; } // Embed lock into hash - hashes[f.path] = await generateHash(await f.getContentText() + (reqs ?? "")); + hashes[f.path] = await generateHash( + (await f.getContentText()) + (reqs ?? "") + ); } } return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; @@ -92,7 +99,7 @@ export async function generateFlowLockInternal( }, justUpdateMetadataLock?: boolean, noStaleMessage?: boolean, - useRawReqs?: boolean, + useRawReqs?: boolean ): Promise { if (folder.endsWith(SEP)) { folder = folder.substring(0, folder.length - 1); @@ -136,16 +143,20 @@ export async function generateFlowLockInternal( } if (useRawReqs) { - log.warn("If using local lockfiles, following redeployments from Web App will inevitably override generated lockfiles by CLI. To maintain your script's lockfiles you will need to redeploy only from CLI. (Behavior is subject to change)") + log.warn( + "If using local lockfiles, following redeployments from Web App will inevitably override generated lockfiles by CLI. To maintain your script's lockfiles you will need to redeploy only from CLI. (Behavior is subject to change)" + ); log.info( (await blueColor())( - `Found raw requirements (${languagesWithRawReqsSupport.map((l) => l.rrFilename).join("/")}) for ${folder}, using it`, - ), + `Found raw requirements (${languagesWithRawReqsSupport + .map((l) => l.rrFilename) + .join("/")}) for ${folder}, using it` + ) ); } const flowValue = (await yamlParseFile( - folder! + SEP + "flow.yaml", + folder! + SEP + "flow.yaml" )) as FlowFile; if (!justUpdateMetadataLock) { @@ -164,7 +175,7 @@ export async function generateFlowLockInternal( replaceInlineScripts( flowValue.value.modules, folder + SEP!, - changedScripts, + changedScripts ); //removeChangedLocks @@ -172,28 +183,26 @@ export async function generateFlowLockInternal( workspace, flowValue.value, remote_path, - rawReqs, + rawReqs ); const inlineScripts = extractInlineScriptsForFlows( flowValue.value.modules, - newPathAssigner(opts.defaultTs ?? "bun"), + newPathAssigner(opts.defaultTs ?? "bun") ); inlineScripts .filter((s) => s.path.endsWith(".lock")) .forEach((s) => { Deno.writeTextFileSync( Deno.cwd() + SEP + folder + SEP + s.path, - s.content, + s.content ); }); // Overwrite `flow.yaml` with the new lockfile references await Deno.writeTextFile( Deno.cwd() + SEP + folder + SEP + "flow.yaml", - yamlStringify( - flowValue as Record - ) + yamlStringify(flowValue as Record) ); } @@ -223,7 +232,7 @@ export async function generateScriptMetadataInternal( noStaleMessage: boolean, globalDeps: GlobalDeps, codebases: SyncCodebase[], - justUpdateMetadataLock?: boolean, + justUpdateMetadataLock?: boolean ): Promise { const remotePath = scriptPath .substring(0, scriptPath.indexOf(".")) @@ -231,26 +240,24 @@ export async function generateScriptMetadataInternal( const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs); - const rrLang = languagesWithRawReqsSupport.find((l) => language == l.language); - - const rawReqs = findClosestRawReqs( - rrLang, - scriptPath, - globalDeps, + const rrLang = languagesWithRawReqsSupport.find( + (l) => language == l.language ); + const rawReqs = findClosestRawReqs(rrLang, scriptPath, globalDeps); + if (rawReqs && rrLang) { log.info( (await blueColor())( - `Found raw requirements (${rrLang.rrFilename}) for ${scriptPath}, using it`, - ), + `Found raw requirements (${rrLang.rrFilename}) for ${scriptPath}, using it` + ) ); } const metadataWithType = await parseMetadataFile( remotePath, undefined, globalDeps, - codebases, + codebases ); // read script content @@ -262,7 +269,7 @@ export async function generateScriptMetadataInternal( if (await checkifMetadataUptodate(remotePath, hash, undefined)) { if (!noStaleMessage) { log.info( - colors.green(`Script ${remotePath} metadata is up-to-date, skipping`), + colors.green(`Script ${remotePath} metadata is up-to-date, skipping`) ); } return; @@ -284,7 +291,7 @@ export async function generateScriptMetadataInternal( scriptContent, language, metadataParsedContent, - scriptPath, + scriptPath ); } @@ -298,7 +305,7 @@ export async function generateScriptMetadataInternal( language, remotePath, metadataParsedContent, - rawReqs, + rawReqs ); } else { metadataParsedContent.lock = ""; @@ -319,7 +326,7 @@ export async function generateScriptMetadataInternal( hash = await generateScriptHash( rawReqs, scriptContent, - metadataContentUsedForHash, + metadataContentUsedForHash ); await updateMetadataGlobalLock(remotePath, hash); if (!justUpdateMetadataLock) { @@ -332,14 +339,14 @@ export async function updateScriptSchema( scriptContent: string, language: ScriptLanguage, metadataContent: Record, - path: string, + path: string ): Promise { // infer schema from script content and update it inplace const result = await inferSchema( language, scriptContent, metadataContent.schema, - path, + path ); metadataContent.schema = result.schema; if (result.has_preprocessor) { @@ -360,7 +367,7 @@ async function updateScriptLock( language: ScriptLanguage, remotePath: string, metadataContent: Record, - rawDeps: string | undefined, + rawDeps: string | undefined ): Promise { if ( !( @@ -393,7 +400,7 @@ async function updateScriptLock( raw_deps: rawDeps, entrypoint: remotePath, }), - }, + } ); let responseText = "reading response failed"; @@ -404,11 +411,11 @@ async function updateScriptLock( if (lock === undefined) { if (response?.["error"]?.["message"]) { throw new LockfileGenerationError( - `Failed to generate lockfile: ${response?.["error"]?.["message"]}`, + `Failed to generate lockfile: ${response?.["error"]?.["message"]}` ); } throw new LockfileGenerationError( - `Failed to generate lockfile: ${JSON.stringify(response, null, 2)}`, + `Failed to generate lockfile: ${JSON.stringify(response, null, 2)}` ); } const lockPath = remotePath + ".script.lock"; @@ -420,7 +427,7 @@ async function updateScriptLock( if (await Deno.stat(lockPath)) { await Deno.remove(lockPath); } - } catch { } + } catch {} metadataContent.lock = ""; } } catch (e) { @@ -428,7 +435,7 @@ async function updateScriptLock( throw e; } throw new LockfileGenerationError( - `Failed to generate lockfile:${rawResponse.statusText}, ${responseText}, ${e}`, + `Failed to generate lockfile:${rawResponse.statusText}, ${responseText}, ${e}` ); } } @@ -459,7 +466,7 @@ export async function updateFlow( use_local_lockfiles: true, raw_deps: rawDeps, }), - }, + } ); } else { // Standard dependency resolution on the server @@ -475,7 +482,7 @@ export async function updateFlow( flow_value, path: remotePath, }), - }, + } ); } @@ -489,20 +496,20 @@ export async function updateFlow( const msg = (res as any)?.["error"]?.["message"]; if (msg) { throw new LockfileGenerationError( - `Failed to generate lockfile: ${msg}`, + `Failed to generate lockfile: ${msg}` ); } throw new LockfileGenerationError( - `Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}`, + `Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}` ); } return (res as any).updated_flow_value; } catch (e) { try { responseText = await rawResponse.text(); - } catch { } + } catch {} throw new Error( - `Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`, + `Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}` ); } } @@ -514,7 +521,7 @@ export async function inferSchema( language: ScriptLanguage, content: string, currentSchema: any, - path: string, + path: string ): Promise<{ schema: any; has_preprocessor: boolean | undefined; @@ -640,8 +647,8 @@ export async function inferSchema( if (inferedSchema.type == "Invalid") { log.info( colors.yellow( - `Script ${path} invalid, it cannot be parsed to infer schema.`, - ), + `Script ${path} invalid, it cannot be parsed to infer schema.` + ) ); return { schema: defaultScriptMetadata().schema, @@ -655,7 +662,7 @@ export async function inferSchema( } currentSchema.required = []; const oldProperties = JSON.parse( - JSON.stringify(currentSchema?.properties ?? {}), + JSON.stringify(currentSchema?.properties ?? {}) ); currentSchema.properties = {}; @@ -666,7 +673,7 @@ export async function inferSchema( currentSchema.properties[arg.name] = oldProperties[arg.name]; } currentSchema.properties[arg.name] = sortObject( - currentSchema.properties[arg.name], + currentSchema.properties[arg.name] ); argSigToJsonSchemaType(arg.typ, currentSchema.properties[arg.name]); @@ -693,7 +700,7 @@ function sortObject(obj: any): any { ...acc, [key]: obj[key], }), - {}, + {} ); } @@ -703,24 +710,30 @@ export function argSigToJsonSchemaType( | string | { resource: string | null } | { - list: - | (string | { object: { key: string; typ: any }[] }) - | { str: any } - | { object: { key: string; typ: any }[] } - | null; - } + list: + | ( + | string + | { + object: { + name?: string; + props?: { key: string; typ: any }[]; + }; + } + ) + | { str: any } + | { object: { name?: string; props?: { key: string; typ: any }[] } } + | null; + } | { dynselect: string } | { str: string[] | null } - | { object: { key: string; typ: any }[] } + | { object: { name?: string; props?: { key: string; typ: any }[] } } | { - oneof: [ - { + oneof: { label: string; properties: { key: string; typ: any }[]; - }, - ]; - }, - oldS: SchemaProperty, + }[]; + }, + oldS: SchemaProperty ): void { const newS: SchemaProperty = { type: "" }; if (t === "int") { @@ -770,9 +783,12 @@ export function argSigToJsonSchemaType( } } else if (typeof t !== "string" && `object` in t) { newS.type = "object"; - if (t.object) { + if (t.object.name) { + newS.format = `resource-${t.object.name}`; + } + if (t.object.props) { const properties: Record = {}; - for (const prop of t.object) { + for (const prop of t.object.props) { if (oldS.properties && prop.key in oldS.properties) { properties[prop.key] = oldS.properties[prop.key]; } else { @@ -804,12 +820,24 @@ export function argSigToJsonSchemaType( newS.type = "array"; if (t.list === "int" || t.list === "float") { newS.items = { type: "number" }; + newS.originalType = "number[]"; } else if (t.list === "bytes") { newS.items = { type: "string", contentEncoding: "base64" }; - } else if (t.list == "string") { - newS.items = { type: "string" }; - } else if (t.list && typeof t.list == "object" && "str" in t.list) { + newS.originalType = "bytes[]"; + } else if ( + t.list && + typeof t.list == "object" && + "str" in t.list && + t.list.str + ) { newS.items = { type: "string", enum: t.list.str }; + newS.originalType = "enum[]"; + } else if ( + t.list == "string" || + (t.list && typeof t.list == "object" && "str" in t.list) + ) { + newS.items = { type: "string", enum: oldS.items?.enum }; + newS.originalType = "string[]"; } else if ( t.list && typeof t.list == "object" && @@ -820,23 +848,30 @@ export function argSigToJsonSchemaType( type: "resource", resourceType: t.list.resource as string, }; + newS.originalType = "resource[]"; } else if ( t.list && typeof t.list == "object" && "object" in t.list && - t.list.object && - t.list.object.length > 0 + t.list.object ) { - const properties: Record = {}; - for (const prop of t.list.object) { - properties[prop.key] = { description: "", type: "" }; - - argSigToJsonSchemaType(prop.typ, properties[prop.key]); + if (t.list.object.name) { + newS.format = `resource-${t.list.object.name}`; } - - newS.items = { type: "object", properties: properties }; + if (t.list.object.props && t.list.object.props.length > 0) { + const properties: Record = {}; + for (const prop of t.list.object.props) { + properties[prop.key] = { description: "", type: "" }; + argSigToJsonSchemaType(prop.typ, properties[prop.key]); + } + newS.items = { type: "object", properties: properties }; + } else { + newS.items = { type: "object" }; + } + newS.originalType = "record[]"; } else { newS.items = { type: "object" }; + newS.originalType = "object[]"; } } else { newS.type = "object"; @@ -886,16 +921,15 @@ export function argSigToJsonSchemaType( delete oldS.items; } - Object.assign(oldS, newS); + if (oldS.format && !newS.format) { + oldS.format = undefined + } + Object.assign(oldS, newS); // if (sameItems && savedItems != undefined && savedItems.enum != undefined) { // sendUserToast(JSON.stringify(savedItems)) // oldS.items = savedItems // } - - if (oldS.format?.startsWith("resource-") && newS.type != "object") { - oldS.format = undefined; - } } //////////////////////////////////////////////////////////////////////////////////////////// @@ -912,9 +946,7 @@ export function replaceLock(o?: { lock?: string | string[] }) { o.lock = readInlinePathSync(lockPath); } catch (e) { log.info( - colors.yellow( - `Failed to read lockfile, doing as if it was empty: ${e}`, - ), + colors.yellow(`Failed to read lockfile, doing as if it was empty: ${e}`) ); o.lock = ""; } @@ -924,13 +956,13 @@ export async function parseMetadataFile( scriptPath: string, generateMetadataIfMissing: | (GlobalOptions & { - path: string; - workspaceRemote: Workspace; - schemaOnly?: boolean; - }) + path: string; + workspaceRemote: Workspace; + schemaOnly?: boolean; + }) | undefined, globalDeps: GlobalDeps, - codebases: SyncCodebase[], + codebases: SyncCodebase[] ): Promise<{ isJson: boolean; payload: any; path: string }> { let metadataFilePath = scriptPath + ".script.json"; try { @@ -956,14 +988,14 @@ export async function parseMetadataFile( // no metadata file at all. Create it log.info( (await blueColor())( - `Creating script metadata file for ${metadataFilePath}`, - ), + `Creating script metadata file for ${metadataFilePath}` + ) ); metadataFilePath = scriptPath + ".script.yaml"; let scriptInitialMetadata = defaultScriptMetadata(); const scriptInitialMetadataYaml = yamlStringify( scriptInitialMetadata as Record, - yamlOptions, + yamlOptions ); await Deno.writeTextFile(metadataFilePath, scriptInitialMetadataYaml, { createNew: true, @@ -972,8 +1004,8 @@ export async function parseMetadataFile( if (generateMetadataIfMissing) { log.info( (await blueColor())( - `Generating lockfile and schema for ${metadataFilePath}`, - ), + `Generating lockfile and schema for ${metadataFilePath}` + ) ); try { await generateScriptMetadataInternal( @@ -984,10 +1016,10 @@ export async function parseMetadataFile( false, globalDeps, codebases, - false, + false ); scriptInitialMetadata = (await yamlParseFile( - metadataFilePath, + metadataFilePath )) as ScriptMetadata; if (!generateMetadataIfMissing.schemaOnly) { replaceLock(scriptInitialMetadata); @@ -995,8 +1027,8 @@ export async function parseMetadataFile( } catch (e) { log.info( colors.yellow( - `Failed to generate lockfile and schema for ${metadataFilePath}: ${e}`, - ), + `Failed to generate lockfile and schema for ${metadataFilePath}: ${e}` + ) ); } } @@ -1035,7 +1067,7 @@ export async function checkifMetadataUptodate( path: string, hash: string, conf: Lock | undefined, - subpath?: string, + subpath?: string ) { if (!conf) { conf = await readLockfile(); @@ -1051,17 +1083,17 @@ export async function checkifMetadataUptodate( export async function generateScriptHash( rawReqs: string | undefined, scriptContent: string, - newMetadataContent: string, + newMetadataContent: string ) { return await generateHash( - (rawReqs ?? "") + scriptContent + newMetadataContent, + (rawReqs ?? "") + scriptContent + newMetadataContent ); } export async function updateMetadataGlobalLock( path: string, hash: string, - subpath?: string, + subpath?: string ): Promise { const conf = await readLockfile(); if (!conf?.locks) { @@ -1080,6 +1112,6 @@ export async function updateMetadataGlobalLock( } await Deno.writeTextFile( WMILL_LOCKFILE, - yamlStringify(conf as Record, yamlOptions), + yamlStringify(conf as Record, yamlOptions) ); } diff --git a/cli/wasm/csharp/windmill_parser_wasm_bg.wasm b/cli/wasm/csharp/windmill_parser_wasm_bg.wasm index a628388cf9..0240124180 100644 Binary files a/cli/wasm/csharp/windmill_parser_wasm_bg.wasm and b/cli/wasm/csharp/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/go/windmill_parser_wasm_bg.wasm b/cli/wasm/go/windmill_parser_wasm_bg.wasm index 160ed99326..9315e8faa5 100644 Binary files a/cli/wasm/go/windmill_parser_wasm_bg.wasm and b/cli/wasm/go/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/java/windmill_parser_wasm_bg.wasm b/cli/wasm/java/windmill_parser_wasm_bg.wasm index 5395c1e789..54606fddf0 100644 Binary files a/cli/wasm/java/windmill_parser_wasm_bg.wasm and b/cli/wasm/java/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/nu/windmill_parser_wasm_bg.wasm b/cli/wasm/nu/windmill_parser_wasm_bg.wasm index 520acc48c4..683fd3d380 100644 Binary files a/cli/wasm/nu/windmill_parser_wasm_bg.wasm and b/cli/wasm/nu/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/php/windmill_parser_wasm_bg.wasm b/cli/wasm/php/windmill_parser_wasm_bg.wasm index 9ca97f73b7..2d772cc834 100644 Binary files a/cli/wasm/php/windmill_parser_wasm_bg.wasm and b/cli/wasm/php/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/py/windmill_parser_wasm_bg.wasm b/cli/wasm/py/windmill_parser_wasm_bg.wasm index cf50952c80..afeb717c24 100644 Binary files a/cli/wasm/py/windmill_parser_wasm_bg.wasm and b/cli/wasm/py/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/regex/windmill_parser_wasm_bg.wasm b/cli/wasm/regex/windmill_parser_wasm_bg.wasm index 7744067327..67de5f24de 100644 Binary files a/cli/wasm/regex/windmill_parser_wasm_bg.wasm and b/cli/wasm/regex/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/rust/windmill_parser_wasm_bg.wasm b/cli/wasm/rust/windmill_parser_wasm_bg.wasm index 5448399f37..1ad42fe519 100644 Binary files a/cli/wasm/rust/windmill_parser_wasm_bg.wasm and b/cli/wasm/rust/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/ts/windmill_parser_wasm.js b/cli/wasm/ts/windmill_parser_wasm.js index fb7493f568..7cad1331c3 100644 --- a/cli/wasm/ts/windmill_parser_wasm.js +++ b/cli/wasm/ts/windmill_parser_wasm.js @@ -254,7 +254,7 @@ const imports = { const ret = Object.entries(arg0); return ret; }, - __wbg_eval_b297c6e75720318e: function(arg0, arg1) { + __wbg_eval_5e26645562cd9430: function(arg0, arg1) { const ret = eval(getStringFromWasm0(arg0, arg1)); return ret; }, diff --git a/cli/wasm/ts/windmill_parser_wasm_bg.wasm b/cli/wasm/ts/windmill_parser_wasm_bg.wasm index 46e8bc4d75..78d98a7863 100644 Binary files a/cli/wasm/ts/windmill_parser_wasm_bg.wasm and b/cli/wasm/ts/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/yaml/windmill_parser_wasm_bg.wasm b/cli/wasm/yaml/windmill_parser_wasm_bg.wasm index 40e1226b48..7ec10c1ab5 100644 Binary files a/cli/wasm/yaml/windmill_parser_wasm_bg.wasm and b/cli/wasm/yaml/windmill_parser_wasm_bg.wasm differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 00e32c1220..163d59cf29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -74,17 +74,17 @@ "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.1.0", "vscode-ws-jsonrpc": "~3.4.0", - "windmill-parser-wasm-csharp": "^1.437.1", - "windmill-parser-wasm-go": "^1.429.0", - "windmill-parser-wasm-java": "^1.478.1", - "windmill-parser-wasm-nu": "^1.474.1", - "windmill-parser-wasm-php": "^1.429.0", - "windmill-parser-wasm-py": "^1.510.0", - "windmill-parser-wasm-regex": "^1.510.0", - "windmill-parser-wasm-rust": "^1.429.0", - "windmill-parser-wasm-ts": "^1.510.0", - "windmill-parser-wasm-yaml": "^1.429.0", - "windmill-sql-datatype-parser-wasm": "^1.318.0", + "windmill-parser-wasm-csharp": "1.510.1", + "windmill-parser-wasm-go": "1.510.1", + "windmill-parser-wasm-java": "1.510.1", + "windmill-parser-wasm-nu": "1.510.1", + "windmill-parser-wasm-php": "1.510.1", + "windmill-parser-wasm-py": "1.510.1", + "windmill-parser-wasm-regex": "1.510.1", + "windmill-parser-wasm-rust": "1.510.1", + "windmill-parser-wasm-ts": "1.510.1", + "windmill-parser-wasm-yaml": "1.510.1", + "windmill-sql-datatype-parser-wasm": "1.318.0", "xterm": "^5.3.0", "xterm-readline": "^1.1.2", "y-monaco": "^0.1.4", @@ -12940,54 +12940,54 @@ } }, "node_modules/windmill-parser-wasm-csharp": { - "version": "1.437.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-csharp/-/windmill-parser-wasm-csharp-1.437.1.tgz", - "integrity": "sha512-qzB/kUE9JCf1CYFDz+50AI+SUVaZYn3lSgqmJ11Iuibl41AC4EIvfH4zrsOU53lcTOb9b4ZH3D6z9FjCCfqWsw==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-csharp/-/windmill-parser-wasm-csharp-1.510.1.tgz", + "integrity": "sha512-qm09YmnbeYHLwYn1jUnObVzPhYO9NZKMlIO7nlo7zPJBXqksgG5fK/KCtwGw9rChrnz+DsvM9wP5FhrwRLMtwQ==" }, "node_modules/windmill-parser-wasm-go": { - "version": "1.429.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-go/-/windmill-parser-wasm-go-1.429.0.tgz", - "integrity": "sha512-M3jeGDqeTyPj9HyyX3msdzMrqIIzlfMfxTMsXS8m7MJp4Cm60qifMxD29Ipxb2B4WdzyGwCSlaBjLsXu0b3c5g==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-go/-/windmill-parser-wasm-go-1.510.1.tgz", + "integrity": "sha512-HOkk6LXK0wrwvkn+zjm3Gxo90HmyL6TYqmLo2yp8fZuppy7GOngT27zwYeBtwONiPyvDKskzoqPQoEfd8VuUsQ==" }, "node_modules/windmill-parser-wasm-java": { - "version": "1.478.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-java/-/windmill-parser-wasm-java-1.478.1.tgz", - "integrity": "sha512-2gMr5pXaEExoLjeSMOpHzY4UjDU/JGjvlXSnBiQDHUaEoKSS7FWa5APzBKcmn8cM+mr2p8m4sMCyTXS88At0fQ==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-java/-/windmill-parser-wasm-java-1.510.1.tgz", + "integrity": "sha512-Zle+JZT/ZwUArUVacUudYlS+CaHp2lSnkqD/IhWaRUG+gcv26VbERnrrHPonqXbVMS+eA9ElfXrFM5j0ukaXUw==" }, "node_modules/windmill-parser-wasm-nu": { - "version": "1.474.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-nu/-/windmill-parser-wasm-nu-1.474.1.tgz", - "integrity": "sha512-ikRhz9CWpWilt/7K8mmRniF2cdWnH6EgjuL189z76pcEQbuMwDO4eXocAdMDaufehOsTWpXmDtsYeFfmayPIpQ==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-nu/-/windmill-parser-wasm-nu-1.510.1.tgz", + "integrity": "sha512-AJLFiUy6af+LpUe7CddDo4+JOmw3c0K/1iOWh8NdTwXcLDj90lL6089mdsVo1apyloLgrTbcuFDzZMXVGBgtCg==" }, "node_modules/windmill-parser-wasm-php": { - "version": "1.429.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-php/-/windmill-parser-wasm-php-1.429.0.tgz", - "integrity": "sha512-SGJAtNpfdRZftkGboxWsm/yQDnJBJodwPQUbX2cWk/aoNook6ULesZwsYtBC9WN1VH6TIskLiVPohMmu6jtXmw==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-php/-/windmill-parser-wasm-php-1.510.1.tgz", + "integrity": "sha512-qM+yeaqPdMuAaPpqND31ZabpeHlPxxtmRWLs11cGHOCHU32FIaZ92/icNUoAxgBhdJjRhR4GXaeZG32nWit2cg==" }, "node_modules/windmill-parser-wasm-py": { - "version": "1.510.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.510.0.tgz", - "integrity": "sha512-zH7p8POSO5gsTC94ZlyfVWecre0JuNHHfBMbotOV533J7PpSgGR20s+NfyNwlT7n4KmSdVmmxUMkmWAkeg8kOA==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.510.1.tgz", + "integrity": "sha512-0ES6W1l/j3NjvHgGX0l6wHvioMX3SsTWe4kQIkYP12ISpAIKfSvDpE2sFm6FQ+xoxekzGqYS4luPSmKX4aDYpQ==" }, "node_modules/windmill-parser-wasm-regex": { - "version": "1.510.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.510.0.tgz", - "integrity": "sha512-AiM+oPbojiFsnK1dfZurc/AJdGMpUs8yOaSLOs55DvJD3Pm1L0WMSfBrti0PTUY1hrKmtavfWgGU7dlOeNz+rg==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.510.1.tgz", + "integrity": "sha512-CUH5+DVramdKD1zVQteWIdwJ2RK9vlfipwYKvXI33DCEAUupQdLdCEyLd8Ics0aZBOrpDoWpgILqkmzPKdRJEg==" }, "node_modules/windmill-parser-wasm-rust": { - "version": "1.429.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-rust/-/windmill-parser-wasm-rust-1.429.0.tgz", - "integrity": "sha512-c8mjpiw8RxoaBDtecb+sKeWM/IOjNr4Y06nHudGu8sMM48MNO1LhgcISLv8wl6Z9zWd7OzQrECJ6RLorpii5Uw==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-rust/-/windmill-parser-wasm-rust-1.510.1.tgz", + "integrity": "sha512-tqT+w5gvwiX9NZCzT7iafh7dWrWSs46t+LI+N8/1+QOpv95G9/NJ/m0DIfH9Wyfkvtx0ge6igLMFha2wlPKG+w==" }, "node_modules/windmill-parser-wasm-ts": { - "version": "1.510.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.510.0.tgz", - "integrity": "sha512-uxcE7gghDQ6lmkjemiNjhrnk0SviUwy80k25GERu5Ujliw8ceynw2cAv9k060+fWTcB3UazgNLbrhT9uPe0cQQ==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.510.1.tgz", + "integrity": "sha512-LIXFb/jETo+EOOvcEj7SUqPlRuc63bAZcGBvJDiZ/aUOMdPquCL4/7Eu+JzTrb8lSJpFu3ZU0ToUFCLIWzhn1w==" }, "node_modules/windmill-parser-wasm-yaml": { - "version": "1.429.0", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.429.0.tgz", - "integrity": "sha512-elQYkaWOvzB8LiwVV9NbNqrupSmdtRY3mMEl+qmKTJhGLvYrVOxA8zyBtwGK0MGiFhTOO3ZO96LWSlDfBnpN9g==" + "version": "1.510.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-yaml/-/windmill-parser-wasm-yaml-1.510.1.tgz", + "integrity": "sha512-zQ1imcKrhP3iccJ01BK0+tptguo3Xc+J5ku2lgrZ+YQdDcC2wjGb6gH+kvcsMXDE3WT4aRwV3nL+ecHa7WHSrw==" }, "node_modules/windmill-sql-datatype-parser-wasm": { "version": "1.318.0", diff --git a/frontend/package.json b/frontend/package.json index b9d18e34cc..29bacb2caf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -141,17 +141,17 @@ "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.1.0", "vscode-ws-jsonrpc": "~3.4.0", - "windmill-parser-wasm-csharp": "^1.437.1", - "windmill-parser-wasm-go": "^1.429.0", - "windmill-parser-wasm-java": "^1.478.1", - "windmill-parser-wasm-nu": "^1.474.1", - "windmill-parser-wasm-php": "^1.429.0", - "windmill-parser-wasm-py": "^1.510.0", - "windmill-parser-wasm-regex": "^1.510.0", - "windmill-parser-wasm-rust": "^1.429.0", - "windmill-parser-wasm-ts": "^1.510.0", - "windmill-parser-wasm-yaml": "^1.429.0", - "windmill-sql-datatype-parser-wasm": "^1.318.0", + "windmill-parser-wasm-csharp": "1.510.1", + "windmill-parser-wasm-go": "1.510.1", + "windmill-parser-wasm-java": "1.510.1", + "windmill-parser-wasm-nu": "1.510.1", + "windmill-parser-wasm-php": "1.510.1", + "windmill-parser-wasm-py": "1.510.1", + "windmill-parser-wasm-regex": "1.510.1", + "windmill-parser-wasm-rust": "1.510.1", + "windmill-parser-wasm-ts": "1.510.1", + "windmill-parser-wasm-yaml": "1.510.1", + "windmill-sql-datatype-parser-wasm": "1.318.0", "xterm": "^5.3.0", "xterm-readline": "^1.1.2", "y-monaco": "^0.1.4", diff --git a/frontend/src/lib/components/ArgInput.svelte b/frontend/src/lib/components/ArgInput.svelte index be560d263f..81189d614e 100644 --- a/frontend/src/lib/components/ArgInput.svelte +++ b/frontend/src/lib/components/ArgInput.svelte @@ -683,7 +683,7 @@ onchange={(x) => fileChanged(x, (val) => (value[i] = val))} multiple={false} /> - {:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined} + {:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined && !(format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))} {#await import('$lib/components/JsonEditor.svelte')} {:then Module} @@ -709,10 +709,16 @@ enum_={itemsType?.enum ?? []} enumLabels={extra['enumLabels']} /> - {:else if itemsType?.type == 'resource' && itemsType?.resourceType && resourceTypes?.includes(itemsType.resourceType)} + {:else if (itemsType?.type == 'resource' && itemsType?.resourceType && resourceTypes?.includes(itemsType.resourceType)) || (format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))} + {@const resourceFormat = + itemsType?.type == 'resource' && + itemsType.resourceType && + resourceTypes.includes(itemsType.resourceType) + ? `resource-${itemsType.resourceType}` + : format!} {:else if itemsType?.type == 'resource'} @@ -780,7 +786,11 @@ if (itemsType?.type == 'number') { value = value.concat(0) } else if ( - itemsType?.type == 'object' || + (itemsType?.type == 'object' && + !( + format?.startsWith('resource-') && + resourceTypes?.includes(format.split('-')[1]) + )) || (itemsType?.type == 'resource' && !( itemsType?.resourceType && resourceTypes?.includes(itemsType?.resourceType) diff --git a/frontend/src/lib/components/SchemaForm.svelte b/frontend/src/lib/components/SchemaForm.svelte index 1255201379..20d6f94f8e 100644 --- a/frontend/src/lib/components/SchemaForm.svelte +++ b/frontend/src/lib/components/SchemaForm.svelte @@ -252,6 +252,7 @@ $effect.pre(() => { isValid = allTrue(inputCheck ?? {}) }) + const actions_render = $derived(actions) diff --git a/frontend/src/lib/inferArgSig.ts b/frontend/src/lib/inferArgSig.ts index 68d3fc814e..6728f9b9d6 100644 --- a/frontend/src/lib/inferArgSig.ts +++ b/frontend/src/lib/inferArgSig.ts @@ -6,14 +6,14 @@ export function argSigToJsonSchemaType( | { resource: string | null } | { list: - | (string | { object: { key: string; typ: any }[] }) + | (string | { name?: string; props?: { key: string; typ: any }[] }) | { str: any } - | { object: { key: string; typ: any }[] } + | { object: { name?: string; props?: { key: string; typ: any }[] } } | null } | { dynselect: string } | { str: string[] | null } - | { object: { key: string; typ: any }[] } + | { object: { name?: string; props?: { key: string; typ: any }[] } } | { oneof: { label: string @@ -69,9 +69,12 @@ export function argSigToJsonSchemaType( } } else if (typeof t !== 'string' && `object` in t) { newS.type = 'object' - if (t.object) { + if (t.object.name) { + newS.format = `resource-${t.object.name}` + } + if (t.object.props) { const properties: Record = {} - for (const prop of t.object) { + for (const prop of t.object.props) { if (oldS.properties && prop.key in oldS.properties) { properties[prop.key] = oldS.properties[prop.key] } else { @@ -119,21 +122,20 @@ export function argSigToJsonSchemaType( resourceType: t.list.resource as string } newS.originalType = 'resource[]' - } else if ( - t.list && - typeof t.list == 'object' && - 'object' in t.list && - t.list.object && - t.list.object.length > 0 - ) { - const properties: Record = {} - for (const prop of t.list.object) { - properties[prop.key] = { description: '', type: '' } - - argSigToJsonSchemaType(prop.typ, properties[prop.key]) + } else if (t.list && typeof t.list == 'object' && 'object' in t.list && t.list.object) { + if (t.list.object.name) { + newS.format = `resource-${t.list.object.name}` + } + if (t.list.object.props && t.list.object.props.length > 0) { + const properties: Record = {} + for (const prop of t.list.object.props) { + properties[prop.key] = { description: '', type: '' } + argSigToJsonSchemaType(prop.typ, properties[prop.key]) + } + newS.items = { type: 'object', properties: properties } + } else { + newS.items = { type: 'object' } } - - newS.items = { type: 'object', properties: properties } newS.originalType = 'record[]' } else { newS.items = { type: 'object' } @@ -184,14 +186,14 @@ export function argSigToJsonSchemaType( delete oldS.items } - Object.assign(oldS, newS) + if (oldS.format && !newS.format) { + oldS.format = undefined + } + Object.assign(oldS, newS) // if (sameItems && savedItems != undefined && savedItems.enum != undefined) { // sendUserToast(JSON.stringify(savedItems)) // oldS.items = savedItems // } - if (oldS.format?.startsWith('resource-') && newS.type != 'object') { - oldS.format = undefined - } }