diff --git a/backend/parsers/windmill-parser-bash/src/lib.rs b/backend/parsers/windmill-parser-bash/src/lib.rs index 5bfe6b20c5..f1652ed5d6 100644 --- a/backend/parsers/windmill-parser-bash/src/lib.rs +++ b/backend/parsers/windmill-parser-bash/src/lib.rs @@ -46,8 +46,6 @@ pub fn parse_powershell_sig(code: &str) -> anyhow::Result { lazy_static::lazy_static! { static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?\r?$"#).unwrap(); - - static ref RE_POWERSHELL_ARGS: Regex = Regex::new(r#"(?:\[([\w\[\]]+)\])?\$(\w+)[\t ]*(?:=[\t ]*(?:(?:(?:"|')([^"\n\r\$]*)(?:"|'))|([\d.]+)))?\r?"#).unwrap(); } fn parse_bash_file(code: &str) -> anyhow::Result>> { @@ -98,10 +96,12 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti let lower_code = code.to_lowercase(); let param_start = lower_code.find("param")?; - // Verify that only comments and whitespace appear before "param" + // Verify that only comments, whitespace, and [CmdletBinding()] appear before "param" let before_param = &code[..param_start]; let mut chars = before_param.chars().peekable(); let mut in_block_comment = false; + let mut in_attribute_bracket = false; + let mut bracket_depth = 0; while let Some(ch) = chars.next() { if in_block_comment { @@ -110,6 +110,19 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti chars.next(); // consume '>' in_block_comment = false; } + } else if in_attribute_bracket { + // Track bracket depth to handle nested brackets/parens in attributes + match ch { + '[' => bracket_depth += 1, + ']' => { + bracket_depth -= 1; + if bracket_depth == 0 { + in_attribute_bracket = false; + } + } + // Allow parentheses inside attributes (e.g., [CmdletBinding()]) + _ => {} + } } else { match ch { // Start of block comment: <# @@ -126,6 +139,11 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti chars.next(); } } + // Start of attribute bracket (e.g., [CmdletBinding()]) + '[' => { + in_attribute_bracket = true; + bracket_depth = 1; + } // Whitespace is allowed c if c.is_whitespace() => {} // Any other character means there's code before param @@ -134,8 +152,8 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti } } - // If we're still in a block comment at the end, it's unclosed - invalid - if in_block_comment { + // If we're still in a block comment or unclosed attribute bracket, it's invalid + if in_block_comment || in_attribute_bracket { return None; } @@ -214,108 +232,269 @@ pub fn extract_powershell_param_block(code: &str, include_keyword: bool) -> Opti None } -enum ParserState { - Normal, - InSingleQuote, - InDoubleQuote, -} -fn split_pwsh_args(code: &str) -> Vec<&str> { - let mut chars = code.char_indices().peekable(); - let mut state = ParserState::Normal; - let mut splits = vec![]; - let mut last_idx = 0; - while let Some((idx, char)) = chars.next() { - match (&state, char) { - (ParserState::Normal, '\'') => { - state = ParserState::InSingleQuote; - } - (ParserState::Normal, '"') => { - state = ParserState::InDoubleQuote; - } - (ParserState::InSingleQuote, '\'') => { - state = ParserState::Normal; - } - (ParserState::InDoubleQuote, '"') => { - state = ParserState::Normal; - } - (ParserState::Normal, ',') => { - splits.push(&code[last_idx..idx]); - last_idx = idx + 1; // skip the comma - } - _ => {} - } - } - - if last_idx < code.len() { - splits.push(&code[last_idx..]); - } - - splits -} - fn parse_powershell_single_typ(typ: &str) -> Typ { match typ.to_lowercase().as_str() { "string" => Typ::Str(None), "int" | "long" => Typ::Int, "decimal" | "double" | "single" => Typ::Float, "datetime" => Typ::Datetime, - "bool" => Typ::Bool, + "bool" | "switch" => Typ::Bool, "pscustomobject" => Typ::Object(ObjectType::new(None, None)), _ => Typ::Str(None), } } -fn parse_powershell_file(code: &str) -> anyhow::Result>> { - let param_wrapper = extract_powershell_param_block(code, false); - let mut args = vec![]; - if let Some(param_wrapper) = param_wrapper { - let params = split_pwsh_args(param_wrapper); - for param in params { - if let Some(cap) = RE_POWERSHELL_ARGS.captures(param) { - let typ = cap.get(1).map(|x| x.as_str().to_string()); - let name = cap.get(2).unwrap().as_str().to_string(); +/// Single-pass PowerShell parameter parser. +/// Parses the content of a param() block and extracts all parameter information. +/// +/// This function processes PowerShell parameter declarations in a single pass, handling: +/// - Parameter attributes: [Parameter(Mandatory)], [Parameter(Mandatory=$true)], [ValidateSet(...)], etc. +/// - Type annotations: [string], [int[]], [PSCustomObject], etc. +/// - Variable names: $Name, $Value, etc. +/// - Default values: = 'text', = 25, = $env:VAR, etc. +/// - Mandatory detection: Parameters with Mandatory attribute are marked as required +fn parse_powershell_parameters(content: &str) -> anyhow::Result> { + #[derive(Debug, PartialEq)] + enum State { + Normal, + InSingleQuote, + InDoubleQuote, + InBracket, + } - let mut parsed_typ = if let Some(typ) = typ { - if typ.as_str().ends_with("[]") { - Some(Typ::List(Box::new(parse_powershell_single_typ( - typ.as_str().strip_suffix("[]").unwrap(), - )))) - } else { - Some(parse_powershell_single_typ(typ.as_str())) + let mut args = Vec::new(); + let mut chars = content.char_indices().peekable(); + let mut state = State::Normal; + let mut bracket_depth: i32 = 0; + let mut paren_depth: i32 = 0; + + // Current parameter being built + let mut type_annotation: Option = None; + let mut var_name: Option = None; + let mut default_value: Option = None; + let mut is_mandatory = false; + + // Track position for extracting text + let mut last_bracket_start = None; + let mut found_dollar = false; + + while let Some((idx, ch)) = chars.next() { + match state { + State::InSingleQuote => { + if ch == '\'' { + state = State::Normal; + } + } + State::InDoubleQuote => { + if ch == '"' { + // Check for escape character + if idx > 0 && content.chars().nth(idx - 1) != Some('`') { + state = State::Normal; } - } else { - None - }; + } + } + State::InBracket => { + match ch { + '[' => bracket_depth += 1, + ']' => { + bracket_depth -= 1; + if bracket_depth == 0 { + // Extract the bracket content + if let Some(start) = last_bracket_start { + let bracket_content = &content[start + 1..idx]; - let default = if let Some(x) = cap.get(3) { - Some(json!(x.as_str().to_string())) - } else if let Some(x) = cap.get(4) { - if parsed_typ.is_none() { - if x.as_str().parse::().is_ok() { - parsed_typ = Some(Typ::Int); - } else if x.as_str().parse::().is_ok() { - parsed_typ = Some(Typ::Float); + // Check if this is a Parameter attribute with Mandatory (case-insensitive) + let lower = bracket_content.to_lowercase(); + if lower.starts_with("parameter(") || lower.starts_with("parameter ") { + // Check for Mandatory (case-insensitive) + if lower.contains("mandatory") { + // Check if it's explicitly set to false + if !lower.contains("mandatory=$false") && !lower.contains("mandatory = $false") { + is_mandatory = true; + } + } + } + + // Check if this looks like a type (simple word, possibly with []) + let is_type = !bracket_content.contains('(') + && !bracket_content.contains('=') + && (bracket_content.chars().next().unwrap_or(' ').is_alphabetic() + || bracket_content.starts_with('[')); + + if is_type && !found_dollar { + type_annotation = Some(bracket_content.to_string()); + } + } + state = State::Normal; + last_bracket_start = None; } } - serde_json::Number::from_str(x.as_str()) - .ok() - .map(serde_json::Value::Number) - } else { - None - }; + '(' => paren_depth += 1, + ')' => paren_depth = paren_depth.saturating_sub(1), + _ => {} + } + } + State::Normal => { + match ch { + '\'' => state = State::InSingleQuote, + '"' => state = State::InDoubleQuote, + '[' => { + state = State::InBracket; + bracket_depth = 1; + last_bracket_start = Some(idx); + } + '$' => { + found_dollar = true; + // Extract variable name + let name_start = idx + 1; + let mut name_end = name_start; + while let Some(&(_, next_ch)) = chars.peek() { + if next_ch.is_alphanumeric() || next_ch == '_' { + name_end += 1; + chars.next(); + } else { + break; + } + } + var_name = Some(content[name_start..name_end].to_string()); + } + '=' if found_dollar => { + // Extract default value + // Skip whitespace after = + while let Some(&(_, next_ch)) = chars.peek() { + if next_ch.is_whitespace() { + chars.next(); + } else { + break; + } + } - args.push(Arg { - name: name, - typ: parsed_typ.unwrap_or(Typ::Str(None)), - default: default.clone(), - otyp: None, - has_default: default.is_some(), - oidx: None, - }); + let default_start = chars.peek().map(|(i, _)| *i).unwrap_or(content.len()); + let mut default_end = default_start; + let mut in_string = false; + let mut string_char = ' '; + + while let Some((i, ch)) = chars.peek().copied() { + if in_string { + if ch == string_char && content.chars().nth(i.saturating_sub(1)) != Some('`') { + in_string = false; + default_end = i + 1; + chars.next(); + } else { + default_end = i + 1; + chars.next(); + } + } else if ch == '\'' || ch == '"' { + in_string = true; + string_char = ch; + default_end = i + 1; + chars.next(); + } else if ch == ',' { + break; + } else if ch.is_whitespace() && chars.clone().skip(1).next().map(|(_, c)| c) == Some(',') { + break; + } else { + default_end = i + 1; + chars.next(); + } + } + + default_value = Some(content[default_start..default_end].trim().to_string()); + } + ',' => { + // End of parameter, finalize it + if let Some(name) = var_name.take() { + args.push(finalize_parameter(name, type_annotation.take(), default_value.take(), is_mandatory)?); + } + + // Reset for next parameter + type_annotation = None; + var_name = None; + default_value = None; + is_mandatory = false; + found_dollar = false; + } + _ => {} + } } } } - Ok(Some(args)) + + // Finalize last parameter + if let Some(name) = var_name { + args.push(finalize_parameter(name, type_annotation, default_value, is_mandatory)?); + } + + Ok(args) +} + +fn finalize_parameter( + name: String, + type_annotation: Option, + default_value: Option, + is_mandatory: bool, +) -> anyhow::Result { + // Store the original PowerShell type for use in the executor + let otyp = type_annotation.clone(); + + let mut parsed_typ = if let Some(typ) = type_annotation { + if typ.ends_with("[]") { + Some(Typ::List(Box::new(parse_powershell_single_typ( + typ.strip_suffix("[]").unwrap(), + )))) + } else { + Some(parse_powershell_single_typ(&typ)) + } + } else { + None + }; + + let default = if let Some(default_str) = default_value { + // Try to parse as string (quoted) + if (default_str.starts_with('"') && default_str.ends_with('"')) + || (default_str.starts_with('\'') && default_str.ends_with('\'')) + { + Some(json!(default_str[1..default_str.len() - 1].to_string())) + } else { + // Try to parse as number + if parsed_typ.is_none() { + if default_str.parse::().is_ok() { + parsed_typ = Some(Typ::Int); + } else if default_str.parse::().is_ok() { + parsed_typ = Some(Typ::Float); + } + } + serde_json::Number::from_str(&default_str) + .ok() + .map(serde_json::Value::Number) + } + } else { + None + }; + + // has_default semantics: + // - true: parameter is optional (has a default value OR is not mandatory) + // - false: parameter is required (marked as Mandatory AND no default value) + // Simplified: A parameter is optional unless it's mandatory without a default + let has_default = default.is_some() || !is_mandatory; + + Ok(Arg { + name, + typ: parsed_typ.unwrap_or(Typ::Str(None)), + default: default.clone(), + otyp, + has_default, + oidx: None, + }) +} + +fn parse_powershell_file(code: &str) -> anyhow::Result>> { + let param_wrapper = extract_powershell_param_block(code, false); + if let Some(param_wrapper) = param_wrapper { + Ok(Some(parse_powershell_parameters(param_wrapper)?)) + } else { + Ok(Some(vec![])) + } } #[cfg(test)] @@ -402,23 +581,23 @@ non_required="${5:-}" star_kwargs: false, args: vec![ Arg { - otyp: None, + otyp: None, // No type annotation name: "Msg".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string".to_string()), // [string] name: "Msg2".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: None, // No type annotation name: "Dflt".to_string(), typ: Typ::Str(None), default: Some(json!("default value, with comma")), @@ -426,7 +605,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: Some("int".to_string()), // [int] name: "Nb".to_string(), typ: Typ::Int, default: Some(json!(3)), @@ -434,7 +613,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // Type inferred from default value name: "Nb2".to_string(), typ: Typ::Float, default: Some(json!(5.0)), @@ -442,7 +621,7 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // Type inferred from default value name: "Nb3".to_string(), typ: Typ::Int, default: Some(json!(5)), @@ -450,35 +629,35 @@ non_required="${5:-}" oidx: None }, Arg { - otyp: None, + otyp: None, // No type annotation name: "Wahoo".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("PSCustomObject".to_string()), // [PSCustomObject] name: "Obj".to_string(), typ: Typ::Object(ObjectType::new(None, None)), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string[]".to_string()), // [string[]] name: "Arr".to_string(), typ: Typ::List(Box::new(Typ::Str(None))), default: None, - has_default: false, + has_default: true, // Optional (not mandatory) oidx: None }, Arg { - otyp: None, + otyp: Some("string".to_string()), // [string] (last type bracket with Mandatory) name: "Message".to_string(), typ: Typ::Str(None), default: None, - has_default: false, + has_default: false, // Required (Mandatory attribute) oidx: None } ], @@ -602,6 +781,242 @@ non_required="${5:-}" extract_powershell_param_block("function test-x{ param($Name)\n}", false), None ); + + // Valid: [CmdletBinding()] before param + assert_eq!( + extract_powershell_param_block("[CmdletBinding()]\nparam($Name)", false), + Some("$Name") + ); + assert_eq!( + extract_powershell_param_block("[CmdletBinding()]\nparam($Name, $Age)", true), + Some("param($Name, $Age)") + ); + + // Valid: [CmdletBinding()] with options before param + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding(SupportsShouldProcess=$true)]\nparam($Path)", + false + ), + Some("$Path") + ); + + // Valid: Multiple attributes before param + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding()]\n[OutputType([string])]\nparam($Value)", + false + ), + Some("$Value") + ); + + // Valid: CmdletBinding with comments + assert_eq!( + extract_powershell_param_block( + "# My function\n[CmdletBinding()]\nparam($Name)", + false + ), + Some("$Name") + ); + + // Valid: CmdletBinding with whitespace variations + assert_eq!( + extract_powershell_param_block( + "[CmdletBinding()] \n param($Name)", + false + ), + Some("$Name") + ); + + // Invalid: Unclosed attribute bracket + assert_eq!( + extract_powershell_param_block("[CmdletBinding(\nparam($Name)", false), + None + ); + } + + #[test] + fn test_parse_powershell_sig_with_parameter_attributes() -> anyhow::Result<()> { + // Test with [Parameter(Mandatory=$true)] attribute + let code = r#"[CmdletBinding()] +param( + [Parameter(Mandatory=$true)] + [string]$Name, + [Parameter(Mandatory=$false)] + [int]$Age = 25 +)"#; + let result = parse_powershell_sig(code)?; + assert_eq!(result.args.len(), 2); + assert_eq!(result.args[0].name, "Name"); + assert_eq!(result.args[0].typ, Typ::Str(None)); + assert_eq!(result.args[0].has_default, false); + assert_eq!(result.args[1].name, "Age"); + assert_eq!(result.args[1].typ, Typ::Int); + assert_eq!(result.args[1].has_default, true); + assert_eq!(result.args[1].default, Some(json!(25))); + + // Test with complex attributes + let code2 = r#"param( + [Parameter(Mandatory=$true, Position=0)] + [ValidateSet('Red', 'Green', 'Blue')] + [string]$Color, + [Parameter(ValueFromPipeline=$true)] + [string[]]$Items +)"#; + let result2 = parse_powershell_sig(code2)?; + assert_eq!(result2.args.len(), 2); + assert_eq!(result2.args[0].name, "Color"); + assert_eq!(result2.args[0].typ, Typ::Str(None)); + assert_eq!(result2.args[1].name, "Items"); + assert_eq!(result2.args[1].typ, Typ::List(Box::new(Typ::Str(None)))); + + Ok(()) + } + + #[test] + fn test_powershell_single_pass_parser() -> anyhow::Result<()> { + // Test the single-pass parser with a complex real-world example + let code = r#"[CmdletBinding()] +param( + [Parameter(Mandatory=$true, Position=0, HelpMessage="Enter the server name")] + [ValidateNotNullOrEmpty()] + [string]$ServerName, + + [Parameter(Mandatory=$false)] + [ValidateRange(1, 65535)] + [int]$Port = 8080, + + [Parameter(ValueFromPipeline=$true)] + [string[]]$LogFiles, + + [ValidateSet('Debug', 'Info', 'Warning', 'Error')] + [string]$LogLevel = 'Info', + + [PSCustomObject]$Config +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 5); + + // ServerName: mandatory string with no default + assert_eq!(result.args[0].name, "ServerName"); + assert_eq!(result.args[0].typ, Typ::Str(None)); + assert_eq!(result.args[0].has_default, false); + + // Port: optional int with default + assert_eq!(result.args[1].name, "Port"); + assert_eq!(result.args[1].typ, Typ::Int); + assert_eq!(result.args[1].default, Some(json!(8080))); + assert_eq!(result.args[1].has_default, true); + + // LogFiles: string array (no mandatory, so optional) + assert_eq!(result.args[2].name, "LogFiles"); + assert_eq!(result.args[2].typ, Typ::List(Box::new(Typ::Str(None)))); + assert_eq!(result.args[2].has_default, true); // Optional (not mandatory) + + // LogLevel: string with default (ValidateSet is ignored but doesn't break parsing) + assert_eq!(result.args[3].name, "LogLevel"); + assert_eq!(result.args[3].typ, Typ::Str(None)); + assert_eq!(result.args[3].default, Some(json!("Info"))); + assert_eq!(result.args[3].has_default, true); + + // Config: PSCustomObject (no mandatory, so optional) + assert_eq!(result.args[4].name, "Config"); + assert_eq!(result.args[4].typ, Typ::Object(ObjectType::new(None, None))); + assert_eq!(result.args[4].has_default, true); // Optional (not mandatory) + + Ok(()) + } + + #[test] + fn test_powershell_mandatory_attribute() -> anyhow::Result<()> { + // Test various forms of the Mandatory attribute + let code = r#"param( + [Parameter(Mandatory)] + [string]$RequiredNoEquals, + + [Parameter(Mandatory=$true)] + [string]$RequiredWithTrue, + + [Parameter(Mandatory = $true)] + [string]$RequiredWithSpaces, + + [Parameter(Mandatory=$false)] + [string]$NotRequired, + + [Parameter(Position=0)] + [string]$NoMandatory, + + [string]$PlainRequired = "default", + + [Parameter(Mandatory=$true)] + [int]$RequiredInt +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 7); + + // RequiredNoEquals: mandatory without =$true + assert_eq!(result.args[0].name, "RequiredNoEquals"); + assert_eq!(result.args[0].has_default, false); // Required (mandatory, no default) + + // RequiredWithTrue: mandatory with =$true + assert_eq!(result.args[1].name, "RequiredWithTrue"); + assert_eq!(result.args[1].has_default, false); // Required + + // RequiredWithSpaces: mandatory with spaces + assert_eq!(result.args[2].name, "RequiredWithSpaces"); + assert_eq!(result.args[2].has_default, false); // Required + + // NotRequired: explicitly Mandatory=$false + assert_eq!(result.args[3].name, "NotRequired"); + assert_eq!(result.args[3].has_default, true); // Optional (not mandatory) + + // NoMandatory: no Mandatory attribute + assert_eq!(result.args[4].name, "NoMandatory"); + assert_eq!(result.args[4].has_default, true); // Optional (not mandatory) + + // PlainRequired: has default value (always optional) + assert_eq!(result.args[5].name, "PlainRequired"); + assert_eq!(result.args[5].has_default, true); // Optional (has default) + assert_eq!(result.args[5].default, Some(json!("default"))); + + // RequiredInt: mandatory int + assert_eq!(result.args[6].name, "RequiredInt"); + assert_eq!(result.args[6].typ, Typ::Int); + assert_eq!(result.args[6].has_default, false); // Required + + Ok(()) + } + + #[test] + fn test_powershell_case_insensitive_parameter() -> anyhow::Result<()> { + // Test that [parameter(...)] is case-insensitive + let code = r#"param( + [parameter(Mandatory)] + [string]$LowerCase, + + [PARAMETER(MANDATORY=$TRUE)] + [string]$UpperCase, + + [Parameter(mandatory=$true)] + [string]$MixedCase +)"#; + let result = parse_powershell_sig(code)?; + + assert_eq!(result.args.len(), 3); + + // All should be detected as mandatory + assert_eq!(result.args[0].name, "LowerCase"); + assert_eq!(result.args[0].has_default, false); + + assert_eq!(result.args[1].name, "UpperCase"); + assert_eq!(result.args[1].has_default, false); + + assert_eq!(result.args[2].name, "MixedCase"); + assert_eq!(result.args[2].has_default, false); + + Ok(()) } #[test] diff --git a/backend/windmill-worker/src/pwsh_executor.rs b/backend/windmill-worker/src/pwsh_executor.rs index 4a4b85f8ac..cda448dc31 100644 --- a/backend/windmill-worker/src/pwsh_executor.rs +++ b/backend/windmill-worker/src/pwsh_executor.rs @@ -255,20 +255,42 @@ pub async fn handle_powershell_job( job.args.as_ref() }; - let args_owned = windmill_parser_bash::parse_powershell_sig(&content)? + let parsed_sig = windmill_parser_bash::parse_powershell_sig(&content)?; + + parsed_sig .args .iter() - .map(|arg| { - ( - arg.name.clone(), - job_args.and_then(|x| x.get(&arg.name).map(|x| raw_to_pwsh_param(x.get()))), - ) - }) - .collect::)>>(); + .filter_map(|arg| { + let value_opt = job_args.and_then(|x| x.get(&arg.name)); - args_owned - .into_iter() - .filter_map(|(n, v)| v.map(|v| format!("-{n} {v}"))) + // Check if this is a switch parameter (only [switch], not [bool]) + let is_switch = arg.otyp.as_ref().map(|t| { + t.to_lowercase() == "switch" + }).unwrap_or(false); + + if is_switch { + // Handle switch parameters: -SwitchName or omit + if let Some(value) = value_opt { + match serde_json::from_str::(value.get()) { + Ok(serde_json::Value::Bool(true)) => { + // Switch is enabled: just pass -SwitchName + Some(format!("-{}", arg.name)) + } + Ok(serde_json::Value::Bool(false)) | _ => { + // Switch is disabled or invalid: omit the parameter + None + } + } + } else { + // No value provided, omit the switch (defaults to false) + None + } + } else { + // Regular parameter (including [bool]): format as -ParamName Value + // For [bool] parameters, this will be -ParamName $true or -ParamName $false + value_opt.map(|v| format!("-{} {}", arg.name, raw_to_pwsh_param(v.get()))) + } + }) .collect::>() .join(" ") }; diff --git a/cli/wasm/regex/windmill_parser_wasm.d.ts b/cli/wasm/regex/windmill_parser_wasm.d.ts index 59f34f2b18..7886800b20 100644 --- a/cli/wasm/regex/windmill_parser_wasm.d.ts +++ b/cli/wasm/regex/windmill_parser_wasm.d.ts @@ -1,14 +1,14 @@ /* tslint:disable */ /* eslint-disable */ -export function parse_assets_sql(code: string): string; -export function parse_db_resource(code: string): string | undefined; -export function parse_bash(code: string): string; export function parse_mssql(code: string): string; -export function parse_oracledb(code: string): string; export function parse_powershell(code: string): string; -export function parse_bigquery(code: string): string; +export function parse_oracledb(code: string): string; export function parse_graphql(code: string): string; -export function parse_snowflake(code: string): string; -export function parse_duckdb(code: string): string; -export function parse_sql(code: string): string; +export function parse_assets_sql(code: string): string; export function parse_mysql(code: string): string; +export function parse_bash(code: string): string; +export function parse_duckdb(code: string): string; +export function parse_db_resource(code: string): string | undefined; +export function parse_bigquery(code: string): string; +export function parse_snowflake(code: string): string; +export function parse_sql(code: string): string; diff --git a/cli/wasm/regex/windmill_parser_wasm.js b/cli/wasm/regex/windmill_parser_wasm.js index 25a60a9b8e..b3b62ebd32 100644 --- a/cli/wasm/regex/windmill_parser_wasm.js +++ b/cli/wasm/regex/windmill_parser_wasm.js @@ -68,60 +68,6 @@ function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0; return decodeText(ptr, len); } -/** - * @param {string} code - * @returns {string} - */ -export function parse_assets_sql(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_assets_sql(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - -/** - * @param {string} code - * @returns {string | undefined} - */ -export function parse_db_resource(code) { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_db_resource(ptr0, len0); - let v2; - if (ret[0] !== 0) { - v2 = getStringFromWasm0(ret[0], ret[1]).slice(); - wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); - } - return v2; -} - -/** - * @param {string} code - * @returns {string} - */ -export function parse_bash(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_bash(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - /** * @param {string} code * @returns {string} @@ -141,25 +87,6 @@ export function parse_mssql(code) { } } -/** - * @param {string} code - * @returns {string} - */ -export function parse_oracledb(code) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_oracledb(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } -} - /** * @param {string} code * @returns {string} @@ -183,13 +110,13 @@ export function parse_powershell(code) { * @param {string} code * @returns {string} */ -export function parse_bigquery(code) { +export function parse_oracledb(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_bigquery(ptr0, len0); + const ret = wasm.parse_oracledb(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -221,13 +148,51 @@ export function parse_graphql(code) { * @param {string} code * @returns {string} */ -export function parse_snowflake(code) { +export function parse_assets_sql(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_snowflake(ptr0, len0); + const ret = wasm.parse_assets_sql(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_mysql(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_mysql(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_bash(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_bash(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -255,17 +220,33 @@ export function parse_duckdb(code) { } } +/** + * @param {string} code + * @returns {string | undefined} + */ +export function parse_db_resource(code) { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_db_resource(ptr0, len0); + let v2; + if (ret[0] !== 0) { + v2 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v2; +} + /** * @param {string} code * @returns {string} */ -export function parse_sql(code) { +export function parse_bigquery(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_sql(ptr0, len0); + const ret = wasm.parse_bigquery(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); @@ -278,13 +259,32 @@ export function parse_sql(code) { * @param {string} code * @returns {string} */ -export function parse_mysql(code) { +export function parse_snowflake(code) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.parse_mysql(ptr0, len0); + const ret = wasm.parse_snowflake(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * @param {string} code + * @returns {string} + */ +export function parse_sql(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_sql(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); diff --git a/cli/wasm/regex/windmill_parser_wasm_bg.wasm b/cli/wasm/regex/windmill_parser_wasm_bg.wasm index 45a144132d..34d6284a98 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/frontend/package-lock.json b/frontend/package-lock.json index 2c35d77034..e60edf14cf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -79,7 +79,7 @@ "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.574.1", "windmill-parser-wasm-py": "1.538.0", - "windmill-parser-wasm-regex": "1.574.1", + "windmill-parser-wasm-regex": "1.575.4", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.565.0", @@ -13759,9 +13759,9 @@ "integrity": "sha512-s+bdIgT/fA5em3zYUwF8D14uA/dZh7iu0krZYZQqZUO7txN37hwSCVfovbMkIwm4zPbsJ50mU8DRLt7UpAPZIw==" }, "node_modules/windmill-parser-wasm-regex": { - "version": "1.574.1", - "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.574.1.tgz", - "integrity": "sha512-KnNBnpTGcnBPSzTQHdsjTPJbRQ81iem98eUnMGO8Zt1qgBFJ/eFZPwmUg6TZMbMITzTi0QqRCJj08eQID1FcPg==" + "version": "1.575.4", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.575.4.tgz", + "integrity": "sha512-G7Ni8XxdSKLNew50B1KRmwn/z9qq3obpEDaaDFS7Z74MeUdaBB/TD69SY6RxDL8SZrh0Tl7SCbS/GGLnt1AzkQ==" }, "node_modules/windmill-parser-wasm-ruby": { "version": "1.526.1", diff --git a/frontend/package.json b/frontend/package.json index 7bd2758467..67a75fac8b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -144,7 +144,7 @@ "windmill-parser-wasm-nu": "1.510.1", "windmill-parser-wasm-php": "1.574.1", "windmill-parser-wasm-py": "1.538.0", - "windmill-parser-wasm-regex": "1.574.1", + "windmill-parser-wasm-regex": "1.575.4", "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.558.1", "windmill-parser-wasm-ts": "1.565.0",