feat: add php (#3725)

* feat: add php

* fix: nsjail + frontend build
This commit is contained in:
HugoCasa
2024-05-15 08:12:11 +02:00
committed by GitHub
parent 4c70c6a4e6
commit 6e805d8252
49 changed files with 1108 additions and 52 deletions
+3
View File
@@ -173,6 +173,9 @@ COPY --from=downloader --chmod=755 /deno /usr/bin/deno
COPY --from=oven/bun:1.1.7 /usr/local/bin/bun /usr/bin/bun
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
# add the docker client to call docker from a worker if enabled
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
@@ -54,7 +54,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -48,7 +48,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -68,7 +68,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -43,7 +43,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -34,7 +34,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -48,7 +48,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -48,7 +48,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -74,7 +74,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
@@ -48,7 +48,8 @@
"snowflake",
"graphql",
"powershell",
"mssql"
"mssql",
"php"
]
}
}
+44
View File
@@ -208,6 +208,15 @@ dependencies = [
"password-hash 0.5.0",
]
[[package]]
name = "ariadne"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c"
dependencies = [
"yansi",
]
[[package]]
name = "array-init"
version = "2.1.0"
@@ -5097,6 +5106,18 @@ dependencies = [
"siphasher",
]
[[package]]
name = "php-parser-rs"
version = "0.1.3"
source = "git+https://github.com/php-rust-tools/parser?rev=ec4cb411dec09450946ef57920b7ffced7f6495d#ec4cb411dec09450946ef57920b7ffced7f6495d"
dependencies = [
"ariadne",
"clap",
"schemars",
"serde",
"serde_json",
]
[[package]]
name = "pin-project"
version = "1.1.5"
@@ -9876,6 +9897,20 @@ dependencies = [
"windmill-parser",
]
[[package]]
name = "windmill-parser-php"
version = "1.327.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
"itertools 0.10.5",
"lazy_static",
"php-parser-rs",
"regex",
"serde_json",
"windmill-parser",
]
[[package]]
name = "windmill-parser-py"
version = "1.329.0"
@@ -9946,6 +9981,7 @@ dependencies = [
"windmill-parser-bash",
"windmill-parser-go",
"windmill-parser-graphql",
"windmill-parser-php",
"windmill-parser-py",
"windmill-parser-sql",
"windmill-parser-ts",
@@ -10006,6 +10042,7 @@ dependencies = [
"bytes",
"chrono",
"const_format",
"convert_case 0.6.0",
"deno_ast",
"deno_console",
"deno_core",
@@ -10056,6 +10093,7 @@ dependencies = [
"windmill-parser-bash",
"windmill-parser-go",
"windmill-parser-graphql",
"windmill-parser-php",
"windmill-parser-py",
"windmill-parser-py-imports",
"windmill-parser-sql",
@@ -10300,6 +10338,12 @@ dependencies = [
"lzma-sys",
]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "yoke"
version = "0.7.3"
+2
View File
@@ -107,6 +107,7 @@ windmill-parser-go = { path = "./parsers/windmill-parser-go" }
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
windmill-parser-php = { path = "./parsers/windmill-parser-php" }
windmill-api-client = { path = "./windmill-api-client" }
axum = { version = "^0.7", features = ["multipart"] }
@@ -139,6 +140,7 @@ rand_core = { version = "^0", features = ["std"] }
magic-crypt = "^3"
git-version = "^0"
rustpython-parser = { git = "https://github.com/RustPython/Parser", rev = "9ce55aefdeb35e2f706ce0b02d5a2dfe6295fc57" }
php-parser-rs = { git = "https://github.com/php-rust-tools/parser", rev = "ec4cb411dec09450946ef57920b7ffced7f6495d" }
cron = "^0"
mail-send = { version = "0.4.0", features = ["builder"], default-features=false }
urlencoding = "^2"
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'php';
@@ -0,0 +1 @@
-- Add down migration script here
@@ -0,0 +1,4 @@
-- Add up migration script here
UPDATE config set config = '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php"]}'::jsonb where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun"]}'::jsonb;
UPDATE workspace_settings SET default_scripts = jsonb_set(default_scripts, '{order}', default_scripts->'order' || '["php"]'::jsonb) WHERE default_scripts IS NOT NULL AND default_scripts->'order' IS NOT NULL AND NOT default_scripts->'order' @> '["php"]'::jsonb;
@@ -0,0 +1,19 @@
[package]
name = "windmill-parser-php"
version.workspace = true
edition.workspace = true
authors.workspace = true
[lib]
name = "windmill_parser_php"
path = "./src/lib.rs"
[dependencies]
windmill-parser.workspace = true
itertools.workspace = true
serde_json.workspace = true
anyhow.workspace = true
php-parser-rs.workspace = true
convert_case.workspace = true
lazy_static.workspace = true
regex.workspace = true
@@ -0,0 +1,166 @@
use convert_case::{Case, Casing};
use regex::Regex;
use serde_json::Value;
use windmill_parser::{Arg, MainArgSignature, Typ};
use php_parser_rs::parser::{
self,
ast::{
data_type::Type,
functions::{FunctionParameterList, FunctionStatement},
literals::Literal,
Expression, Statement,
},
};
lazy_static::lazy_static! {
static ref RE_SNK_CASE: Regex = Regex::new(r"_(\d)").unwrap();
}
fn to_snake_case(s: String) -> String {
let r = s.to_case(Case::Snake);
// s_3 => s3
RE_SNK_CASE.replace_all(&r, "$1").to_string()
}
fn parse_php_type(e: Type) -> Typ {
match e {
Type::Float(_) => Typ::Float,
Type::Boolean(_) => Typ::Bool,
Type::Integer(_) => Typ::Int,
Type::String(_) => Typ::Str(None),
Type::Array(_) => Typ::List(Box::new(Typ::Str(None))),
Type::Object(_) => Typ::Object(vec![]),
Type::Named(_, name) => Typ::Resource(to_snake_case(name.to_string())),
_ => Typ::Unknown,
}
}
fn parse_default_expr(e: Expression) -> Option<Value> {
match e {
Expression::Literal(l) => match l {
Literal::String(s) => Some(Value::String(s.value.to_string())),
Literal::Integer(i) => match i.value.to_string().parse() {
Ok(i) => Some(Value::Number(i)),
Err(_) => None,
},
Literal::Float(f) => match f.value.to_string().parse() {
Ok(i) => Some(Value::Number(i)),
Err(_) => None,
},
},
Expression::Bool(b) => Some(Value::Bool(b.value)),
_ => None,
}
}
pub fn parse_php_signature(
code: &str,
override_main: Option<String>,
) -> anyhow::Result<MainArgSignature> {
let main_name = override_main.unwrap_or("main".to_string());
let ast = parser::parse(code)
.map_err(|e| anyhow::anyhow!("Error parsing code: {}", e.to_string()))?;
let params = ast.into_iter().find_map(|x| match x {
Statement::Function(FunctionStatement {
name,
parameters: FunctionParameterList { parameters, .. },
..
}) if name.to_string() == main_name => Some(parameters),
_ => None,
});
if let Some(params) = params {
let args = params
.into_iter()
.map(|x| {
let typ = x.data_type.map_or(Typ::Unknown, |e| parse_php_type(e));
let default = x.default.map_or(None, |e| parse_default_expr(e));
Arg {
otyp: None,
name: x.name.to_string().trim_start_matches('$').to_string(),
typ,
has_default: default.is_some(),
default,
}
})
.collect();
Ok(MainArgSignature { star_args: false, star_kwargs: false, args })
} else {
Err(anyhow::anyhow!(
"main function was not findable".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use serde_json::Number;
use super::*;
#[test]
fn test_parse_php_sig() -> anyhow::Result<()> {
let code = "
<?php
class Stripe {}
function main(string $input1 = \"hey\", bool $input2 = false, int $input3 = 3, float $input4 = 4.5, Stripe $resource) {
echo 'hello';
}
";
assert_eq!(
parse_php_signature(code, None)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![
Arg {
otyp: None,
name: "input1".to_string(),
typ: Typ::Str(None),
has_default: true,
default: Some(Value::String("hey".to_string()))
},
Arg {
otyp: None,
name: "input2".to_string(),
typ: Typ::Bool,
has_default: true,
default: Some(Value::Bool(false))
},
Arg {
otyp: None,
name: "input3".to_string(),
typ: Typ::Int,
has_default: true,
default: Some(Value::Number(Number::from(3)))
},
Arg {
otyp: None,
name: "input4".to_string(),
typ: Typ::Float,
has_default: true,
default: Some(Value::Number(Number::from_f64(f64::from(4.5)).unwrap()))
},
Arg {
otyp: None,
name: "resource".to_string(),
typ: Typ::Resource("stripe".to_string()),
has_default: false,
default: None
}
]
}
);
Ok(())
}
}
@@ -21,6 +21,7 @@ windmill-parser-bash.workspace = true
windmill-parser-sql.workspace = true
windmill-parser-py.workspace = true
windmill-parser-ts.workspace = true
windmill-parser-php.workspace = true
windmill-parser-graphql.workspace = true
wasm-bindgen.workspace = true
serde_json.workspace = true
@@ -3,7 +3,7 @@
"collaborators": [
"Ruben Fiszel <ruben@windmill.dev>"
],
"version": "1.318.0",
"version": "1.327.0",
"files": [
"windmill_parser_wasm_bg.wasm",
"windmill_parser_wasm.js",
@@ -70,6 +70,11 @@ export function parse_db_resource(code: string): string | undefined;
* @returns {string}
*/
export function parse_graphql(code: string): string;
/**
* @param {string} code
* @returns {string}
*/
export function parse_php(code: string): string;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
@@ -89,6 +94,7 @@ export interface InitOutput {
readonly parse_mssql: (a: number, b: number, c: number) => void;
readonly parse_db_resource: (a: number, b: number, c: number) => void;
readonly parse_graphql: (a: number, b: number, c: number) => void;
readonly parse_php: (a: number, b: number, c: number) => void;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
@@ -97,15 +97,6 @@ function getInt32Memory0() {
return cachedInt32Memory0;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
@@ -115,6 +106,15 @@ function addHeapObject(obj) {
return idx;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let cachedFloat64Memory0 = null;
function getFloat64Memory0() {
@@ -519,6 +519,29 @@ export function parse_graphql(code) {
}
}
/**
* @param {string} code
* @returns {string}
*/
export function parse_php(code) {
let deferred2_0;
let deferred2_1;
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.parse_php(retptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
deferred2_0 = r0;
deferred2_1 = r1;
return getStringFromWasm0(r0, r1);
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
function handleError(f, args) {
try {
return f.apply(this, args);
@@ -564,6 +587,10 @@ function __wbg_get_imports() {
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_eval_f2b8ae7add53626d = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'string' ? obj : undefined;
@@ -572,10 +599,6 @@ function __wbg_get_imports() {
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0);
const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
@@ -597,6 +620,10 @@ function __wbg_get_imports() {
const ret = BigInt.asUintN(64, arg0);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_error_new = function(arg0, arg1) {
const ret = new Error(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
const obj = getObject(arg1);
const ret = typeof(obj) === 'number' ? obj : undefined;
@@ -612,10 +639,6 @@ function __wbg_get_imports() {
const ret = getObject(arg0) in getObject(arg1);
return ret;
};
imports.wbg.__wbg_eval_33c4985197d1feaf = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_jsval_loose_eq = function(arg0, arg1) {
const ret = getObject(arg0) == getObject(arg1);
return ret;
@@ -15,6 +15,7 @@ export function parse_snowflake(a: number, b: number, c: number): void;
export function parse_mssql(a: number, b: number, c: number): void;
export function parse_db_resource(a: number, b: number, c: number): void;
export function parse_graphql(a: number, b: number, c: number): void;
export function parse_php(a: number, b: number, c: number): void;
export function __wbindgen_malloc(a: number, b: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
export function __wbindgen_add_to_stack_pointer(a: number): number;
@@ -92,3 +92,8 @@ pub fn parse_db_resource(code: &str) -> Option<String> {
pub fn parse_graphql(code: &str) -> String {
wrap_sig(windmill_parser_graphql::parse_graphql_sig(code))
}
#[wasm_bindgen]
pub fn parse_php(code: &str) -> String {
wrap_sig(windmill_parser_php::parse_php_signature(code, None))
}
+6
View File
@@ -8406,6 +8406,7 @@ components:
graphql,
nativets,
bun,
php,
]
kind:
type: string
@@ -8501,6 +8502,7 @@ components:
graphql,
nativets,
bun,
php,
]
kind:
type: string
@@ -8719,6 +8721,7 @@ components:
graphql,
nativets,
bun,
php,
]
email:
type: string
@@ -8829,6 +8832,7 @@ components:
graphql,
nativets,
bun,
php,
]
is_skipped:
type: boolean
@@ -9343,6 +9347,7 @@ components:
graphql,
nativets,
bun,
php
]
tag:
type: string
@@ -10380,6 +10385,7 @@ components:
graphql,
nativets,
bun,
php
]
required:
- raw_code
+2 -1
View File
@@ -557,7 +557,8 @@ async fn create_script_internal<'c>(
let lock = if !(ns.language == ScriptLang::Python3
|| ns.language == ScriptLang::Go
|| ns.language == ScriptLang::Bun
|| ns.language == ScriptLang::Deno)
|| ns.language == ScriptLang::Deno
|| ns.language == ScriptLang::Php)
{
Some(String::new())
} else {
+1
View File
@@ -2473,6 +2473,7 @@ async fn tarball_workspace(
"bun.ts"
}
}
ScriptLang::Php => "php",
};
archive
.write_to_archive(&script.content, &format!("{}.{}", script.path, ext))
+2
View File
@@ -39,6 +39,7 @@ pub enum ScriptLang {
Snowflake,
Graphql,
Mssql,
Php,
}
impl ScriptLang {
@@ -57,6 +58,7 @@ impl ScriptLang {
ScriptLang::Snowflake => "snowflake",
ScriptLang::Mssql => "mssql",
ScriptLang::Graphql => "graphql",
ScriptLang::Php => "php",
}
}
}
+2
View File
@@ -29,6 +29,7 @@ windmill-parser-py-imports.workspace = true
windmill-parser-bash.workspace = true
windmill-parser-sql.workspace = true
windmill-parser-graphql.workspace = true
windmill-parser-php.workspace = true
windmill-git-sync.workspace = true
sqlx.workspace = true
uuid.workspace = true
@@ -80,6 +81,7 @@ tokio-util = { workspace = true, optional = true }
openidconnect = { workspace = true, optional = true}
tar = { workspace = true, optional = true}
object_store = { workspace = true, optional = true}
convert_case.workspace = true
[build-dependencies]
deno_fetch.workspace = true
@@ -0,0 +1,136 @@
name: "php run script"
mode: ONCE
hostname: "php"
log_level: ERROR
disable_rl: true
cwd: "/tmp"
clone_newnet: false
clone_newuser: {CLONE_NEWUSER}
keep_caps: false
keep_env: true
mount_proc: true
mount {
src: "/bin"
dst: "/bin"
is_bind: true
}
mount {
src: "/lib"
dst: "/lib"
is_bind: true
}
mount {
src: "/lib64"
dst: "/lib64"
is_bind: true
}
mount {
src: "/usr"
dst: "/usr"
is_bind: true
}
mount {
src: "/dev/null"
dst: "/dev/null"
is_bind: true
rw: true
}
mount {
dst: "/tmp"
fstype: "tmpfs"
rw: true
options: "size=800000000"
}
mount {
src: "{JOB_DIR}/main.php"
dst: "/tmp/main.php"
is_bind: true
mandatory: false
}
mount {
src: "{JOB_DIR}/wrapper.php"
dst: "/tmp/wrapper.php"
is_bind: true
mandatory: false
}
mount {
src: "/etc"
dst: "/etc"
is_bind: true
}
mount {
src: "/dev/random"
dst: "/dev/random"
is_bind: true
}
mount {
src: "/dev/urandom"
dst: "/dev/urandom"
is_bind: true
}
mount {
src: "{JOB_DIR}/args.json"
dst: "/tmp/args.json"
is_bind: true
}
mount {
src: "{JOB_DIR}/result.json"
dst: "/tmp/result.json"
rw: true
is_bind: true
}
mount {
src: "{JOB_DIR}/composer.json"
dst: "/tmp/composer.json"
is_bind: true
mandatory: false
}
mount {
src: "{JOB_DIR}/composer.lock"
dst: "/tmp/composer.lock"
is_bind: true
mandatory: false
}
mount {
src: "{JOB_DIR}/vendor"
dst: "/tmp/vendor"
is_bind: true
mandatory: false
}
iface_no_lo: true
mount {
src: "{CACHE_DIR}"
dst: "/tmp/windmill/cache/php"
is_bind: true
rw: true
mandatory: false
}
{SHARED_MOUNT}
envar: "HOME=/tmp"
+1
View File
@@ -18,6 +18,7 @@ mod graphql_executor;
mod js_eval;
mod mysql_executor;
mod pg_executor;
mod php_executor;
mod python_executor;
mod worker;
mod worker_flow;
+330
View File
@@ -0,0 +1,330 @@
use convert_case::{Case, Casing};
use itertools::Itertools;
use regex::Regex;
use serde_json::value::RawValue;
use std::{collections::HashMap, process::Stdio};
use tokio::{fs::File, io::AsyncReadExt, process::Command};
use uuid::Uuid;
use windmill_common::{
error::{self, to_anyhow, Result},
jobs::QueuedJob,
};
use windmill_parser::Typ;
use windmill_queue::{append_logs, CanceledBy};
use crate::{
common::{
create_args_and_out_file, get_main_override, get_reserved_variables, handle_child,
read_result, start_child_process, write_file,
},
AuthedClientBackgroundTask, COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER,
NSJAIL_PATH, PHP_PATH,
};
const NSJAIL_CONFIG_RUN_PHP_CONTENT: &str = include_str!("../nsjail/run.php.config.proto");
lazy_static::lazy_static! {
static ref RE: Regex = Regex::new(r"^//\s?(\S+)\s*$").unwrap();
}
const COMPOSER_LOCK_SPLIT: &str = "\nLOCK\n";
pub fn parse_php_imports(code: &str) -> anyhow::Result<Option<String>> {
let find_requirements = code
.lines()
.find_position(|x| x.starts_with("//require:") || x.starts_with("// require:"));
if let Some((pos, _)) = find_requirements {
let requirements = code
.lines()
.skip(pos + 1)
.map_while(|x| {
RE.captures(x).map(|x| {
match x.get(1).unwrap().as_str().split("@").collect_vec()[..] {
[path, version] => (path.to_string(), version.to_string()),
[path] | [path, ..] => (path.to_string(), "*".to_string()),
[] => unreachable!(),
}
})
})
.collect::<HashMap<_, _>>();
let composer_json =
serde_json::to_string_pretty(&HashMap::from([("require", requirements)]))
.map_err(to_anyhow)?;
Ok(Some(composer_json))
} else {
Ok(None)
}
}
pub async fn composer_install(
mem_peak: &mut i32,
canceled_by: &mut Option<CanceledBy>,
job_id: &Uuid,
w_id: &str,
db: &sqlx::Pool<sqlx::Postgres>,
job_dir: &str,
worker_name: &str,
requirements: String,
lock: Option<String>,
) -> Result<String> {
write_file(job_dir, "composer.json", &requirements).await?;
if let Some(lock) = lock.as_ref() {
write_file(job_dir, "composer.lock", lock).await?;
}
let mut child_cmd = Command::new(&*COMPOSER_PATH);
let args = vec!["install", "--no-dev", "--no-progress"];
child_cmd
.current_dir(job_dir)
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let child_process = start_child_process(child_cmd, &*COMPOSER_PATH).await?;
handle_child(
job_id,
db,
mem_peak,
canceled_by,
child_process,
false,
worker_name,
w_id,
"composer install",
None,
false,
)
.await?;
match lock {
Some(lock) => Ok(format!("{requirements}{COMPOSER_LOCK_SPLIT}{lock}")),
None => {
let mut lock_content = "".to_string();
let mut lock_file = File::open(format!("{job_dir}/composer.lock")).await?;
lock_file.read_to_string(&mut lock_content).await?;
Ok(format!("{requirements}{COMPOSER_LOCK_SPLIT}{lock_content}"))
}
}
}
fn generate_resource_class(rt_name: &str, arg_name: &str) -> String {
let rt_name = rt_name.to_case(Case::Pascal);
format!(
"#[AllowDynamicProperties]
class {rt_name} {{
public function __construct($data) {{
foreach ($data AS $key => $value) $this->{{$key}} = $value;
}}
}}
$args->{arg_name} = new {rt_name}($args->{arg_name});"
)
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn handle_php_job(
requirements_o: Option<String>,
mem_peak: &mut i32,
canceled_by: &mut Option<CanceledBy>,
job: &QueuedJob,
db: &sqlx::Pool<sqlx::Postgres>,
client: &AuthedClientBackgroundTask,
job_dir: &str,
inner_content: &String,
base_internal_url: &str,
worker_name: &str,
envs: HashMap<String, String>,
shared_mount: &str,
) -> error::Result<Box<RawValue>> {
let (composer_json, composer_lock) = match requirements_o {
Some(reqs_and_lock) if !reqs_and_lock.is_empty() => {
let splitted = reqs_and_lock.split(COMPOSER_LOCK_SPLIT).collect_vec();
if splitted.len() != 2 {
return Err(error::Error::ExecutionErr(
format!("Invalid requirements, expected to find LOCK split pattern in reqs. Found: |{reqs_and_lock}|")
));
}
(Some(splitted[0].to_string()), Some(splitted[1].to_string()))
}
_ => (parse_php_imports(inner_content)?, None),
};
let autoload_line = if let Some(composer_json) = composer_json {
let logs1 = "\n\n--- COMPOSER INSTALL ---\n".to_string();
append_logs(job.id, job.workspace_id.clone(), logs1, db).await;
composer_install(
mem_peak,
canceled_by,
&job.id,
&job.workspace_id,
db,
job_dir,
worker_name,
composer_json,
composer_lock,
)
.await?;
"require './vendor/autoload.php';"
} else {
""
};
let init_logs = "\n\n--- PHP CODE EXECUTION ---\n".to_string();
append_logs(job.id.clone(), job.workspace_id.to_string(), init_logs, db).await;
let _ = write_file(job_dir, "main.php", inner_content).await?;
let main_override = get_main_override(job.args.as_ref());
let write_wrapper_f = async {
let args =
windmill_parser_php::parse_php_signature(inner_content, main_override.clone())?.args;
let args_to_include = args
.iter()
.filter(|x| {
!x.has_default || job.args.as_ref().is_some_and(|a| a.contains_key(&x.name))
})
.collect::<Vec<_>>();
let func_args_str = args_to_include
.iter()
.map(|x| format!("$args->{}", x.name))
.collect::<Vec<String>>()
.join(",");
let resource_classes = args_to_include
.iter()
.filter_map(|x| match &x.typ {
Typ::Resource(name) => Some((name, &x.name)),
_ => None,
})
.unique()
.map(|(rt_name, arg_name)| generate_resource_class(rt_name, arg_name))
.collect::<Vec<_>>()
.join("\n\n");
let main_name = main_override.unwrap_or("main".to_string());
let wrapper_content: String = format!(
r#"
<?php
{autoload_line}
require './main.php';
$args_str = file_get_contents('./args.json');
$args = json_decode($args_str);
{resource_classes}
try {{
$res = {main_name}({func_args_str});
file_put_contents("result.json", json_encode($res));
}} catch (Exception $e) {{
$err = [
"message" => $e->getMessage(),
"name" => $e->getName(),
"stack" => $e->getTraceAsString()
];
$step_id = getenv('WM_FLOW_STEP_ID');
if ($step_id) {{
$err["step_id"] = $step_id;
}}
file_put_contents("result.json", json_encode($err));
exit(1);
}}
"#,
);
write_file(job_dir, "wrapper.php", &wrapper_content).await?;
Ok(()) as error::Result<()>
};
let reserved_variables_args_out_f = async {
let args_and_out_f = async {
create_args_and_out_file(&client, job, job_dir, db).await?;
Ok(()) as Result<()>
};
let reserved_variables_f = async {
let client = client.get_authed().await;
let vars = get_reserved_variables(job, &client.token, db).await?;
Ok(vars) as Result<HashMap<String, String>>
};
let (_, reserved_variables) = tokio::try_join!(args_and_out_f, reserved_variables_f)?;
Ok(reserved_variables) as error::Result<HashMap<String, String>>
};
let (reserved_variables, _) = tokio::try_join!(reserved_variables_args_out_f, write_wrapper_f)?;
let child = if !*DISABLE_NSJAIL {
let _ = write_file(
job_dir,
"run.config.proto",
&NSJAIL_CONFIG_RUN_PHP_CONTENT
.replace("{JOB_DIR}", job_dir)
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
.replace("{SHARED_MOUNT}", shared_mount),
)
.await?;
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
let args = vec![
"--config",
"run.config.proto",
"--",
&PHP_PATH,
"/tmp/wrapper.php",
];
nsjail_cmd
.current_dir(job_dir)
.env_clear()
.envs(envs)
.envs(reserved_variables)
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
.env("BASE_INTERNAL_URL", base_internal_url)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
} else {
let cmd = {
let script_path = format!("{job_dir}/wrapper.php");
let mut php_cmd = Command::new(&*PHP_PATH);
let args = vec![&script_path];
php_cmd
.current_dir(job_dir)
.env_clear()
.envs(envs)
.envs(reserved_variables)
.env("COMPOSER_HOME", &*COMPOSER_CACHE_DIR)
.env("BASE_INTERNAL_URL", base_internal_url)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
php_cmd
};
start_child_process(cmd, &*PHP_PATH).await?
};
handle_child(
&job.id,
db,
mem_peak,
canceled_by,
child,
false,
worker_name,
&job.workspace_id,
"php run",
job.timeout,
false,
)
.await?;
read_result(job_dir).await
}
+49
View File
@@ -100,6 +100,7 @@ use crate::{
js_eval::{eval_fetch_timeout, transpile_ts},
mysql_executor::do_mysql,
pg_executor::do_postgresql,
php_executor::{composer_install, handle_php_job, parse_php_imports},
python_executor::{
create_dependencies_dir, handle_python_job, handle_python_reqs, pip_compile,
},
@@ -203,6 +204,7 @@ pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun");
pub const HUB_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "hub");
pub const GO_BIN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "gobin");
pub const POWERSHELL_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "powershell");
pub const COMPOSER_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "composer");
const NUM_SECS_PING: u64 = 5;
@@ -273,6 +275,8 @@ lazy_static::lazy_static! {
pub static ref NPM_PATH: String = std::env::var("NPM_PATH").unwrap_or_else(|_| "/usr/bin/npm".to_string());
pub static ref NODE_PATH: String = std::env::var("NODE_PATH").unwrap_or_else(|_| "/usr/bin/node".to_string());
pub static ref POWERSHELL_PATH: String = std::env::var("POWERSHELL_PATH").unwrap_or_else(|_| "/usr/bin/pwsh".to_string());
pub static ref PHP_PATH: String = std::env::var("PHP_PATH").unwrap_or_else(|_| "/usr/bin/php".to_string());
pub static ref COMPOSER_PATH: String = std::env::var("COMPOSER_PATH").unwrap_or_else(|_| "/usr/bin/composer".to_string());
pub static ref NSJAIL_PATH: String = std::env::var("NSJAIL_PATH").unwrap_or_else(|_| "nsjail".to_string());
pub static ref PATH_ENV: String = std::env::var("PATH").unwrap_or_else(|_| String::new());
pub static ref HOME_ENV: String = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
@@ -3431,6 +3435,23 @@ mount {{
)
.await
}
Some(ScriptLang::Php) => {
handle_php_job(
requirements_o,
mem_peak,
canceled_by,
job,
db,
client,
job_dir,
&inner_content,
base_internal_url,
worker_name,
envs,
&shared_mount,
)
.await
}
_ => panic!("unreachable, language is not supported: {language:#?}"),
};
tracing::info!(
@@ -4357,6 +4378,34 @@ async fn capture_dependency_job(
.await?;
Ok(req.unwrap_or_else(String::new))
}
ScriptLang::Php => {
let reqs = if raw_deps {
if job_raw_code.is_empty() {
return Ok("".to_string());
}
job_raw_code.to_string()
} else {
match parse_php_imports(job_raw_code)? {
Some(reqs) => reqs,
None => {
return Ok("".to_string());
}
}
};
composer_install(
mem_peak,
canceled_by,
job_id,
w_id,
db,
job_dir,
worker_name,
reqs,
None,
)
.await
}
ScriptLang::Postgresql => Ok("".to_owned()),
ScriptLang::Mysql => Ok("".to_owned()),
ScriptLang::Bigquery => Ok("".to_owned()),
+14 -4
View File
@@ -30,7 +30,7 @@ import { generateHash } from "./utils.ts";
export async function generateAllMetadata() {}
function findClosestRawReqs(
lang: "bun" | "python3" | undefined,
lang: "bun" | "python3" | "php" | undefined,
remotePath: string,
globalDeps: GlobalDeps
): string | undefined {
@@ -53,6 +53,15 @@ function findClosestRawReqs(
bestCandidate = { k, v };
}
});
} else if (lang == "php") {
Object.entries(globalDeps.composers).forEach(([k, v]) => {
if (
remotePath.startsWith(k) &&
k.length >= (bestCandidate?.k ?? "").length
) {
bestCandidate = { k, v };
}
});
}
// @ts-ignore
return bestCandidate?.v;
@@ -77,14 +86,14 @@ export async function generateMetadataInternal(
const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs);
const rawReqs = findClosestRawReqs(
language as "bun" | "python3" | undefined,
language as "bun" | "python3" | "php" | undefined,
scriptPath,
globalDeps
);
if (rawReqs) {
log.info(
colors.blue(
`Found raw requirements (package.json/requirements.txt) for ${scriptPath}, using it`
`Found raw requirements (package.json/requirements.txt/composer.json) for ${scriptPath}, using it`
)
);
}
@@ -184,7 +193,8 @@ async function updateScriptLock(
language == "bun" ||
language == "python3" ||
language == "go" ||
language == "deno"
language == "deno" ||
language == "php"
)
) {
return;
+11 -2
View File
@@ -671,17 +671,24 @@ async function bootstrap(
export type GlobalDeps = {
pkgs: Record<string, string>;
reqs: Record<string, string>;
composers: Record<string, string>;
};
export async function findGlobalDeps(
codebases: SyncCodebase[]
): Promise<GlobalDeps> {
const pkgs: { [key: string]: string } = {};
const reqs: { [key: string]: string } = {};
const composers: { [key: string]: string } = {};
const els = await FSFSElement(Deno.cwd(), codebases);
for await (const entry of readDirRecursiveWithIgnore((p, isDir) => {
p = "/" + p;
return (
!isDir && !(p.endsWith("/package.json") || p.endsWith("requirements.txt"))
!isDir &&
!(
p.endsWith("/package.json") ||
p.endsWith("requirements.txt") ||
p.endsWith("composer.json")
)
);
}, els)) {
if (entry.isDirectory || entry.ignored) continue;
@@ -690,9 +697,11 @@ export async function findGlobalDeps(
pkgs[entry.path.substring(0, entry.path.length - 12)] = content;
} else if (entry.path.endsWith("requirements.txt")) {
reqs[entry.path.substring(0, entry.path.length - 16)] = content;
} else if (entry.path.endsWith("composer.json")) {
composers[entry.path.substring(0, entry.path.length - 13)] = content;
}
}
return { pkgs, reqs };
return { pkgs, reqs, composers };
}
async function generateMetadata(
opts: GlobalOptions & {
+4 -4
View File
@@ -53,7 +53,7 @@
"vscode-languageclient": "~9.0.1",
"vscode-uri": "~3.0.8",
"vscode-ws-jsonrpc": "~3.1.0",
"windmill-parser-wasm": "^1.318.0",
"windmill-parser-wasm": "^1.327.0",
"windmill-sql-datatype-parser-wasm": "^1.318.0",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
@@ -10310,9 +10310,9 @@
}
},
"node_modules/windmill-parser-wasm": {
"version": "1.318.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.318.0.tgz",
"integrity": "sha512-uxiluG0/EIwqwmR4FTppxa2eHZ0hrTijjWmf5L1vJYAgdCotI+glQhjdD2EMNtQRwX9+Pjj8QoVYckrNwJ/2yg=="
"version": "1.327.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm/-/windmill-parser-wasm-1.327.0.tgz",
"integrity": "sha512-lliouG5syBSwaPDN3C5fd2whqZmD5kW7S+gXBtunnMwIydPfXiHqWTL4F2zvbHhcHhiIgN04ATxL7gpxcMP1wg=="
},
"node_modules/windmill-sql-datatype-parser-wasm": {
"version": "1.318.0",
+1 -1
View File
@@ -133,7 +133,7 @@
"vscode-languageclient": "~9.0.1",
"vscode-uri": "~3.0.8",
"vscode-ws-jsonrpc": "~3.1.0",
"windmill-parser-wasm": "^1.318.0",
"windmill-parser-wasm": "^1.327.0",
"windmill-sql-datatype-parser-wasm": "^1.318.0",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
@@ -22,6 +22,7 @@
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
import 'monaco-editor/esm/vs/basic-languages/graphql/graphql.contribution'
import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution'
import 'monaco-editor/esm/vs/basic-languages/php/php.contribution'
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution'
@@ -84,6 +85,7 @@
| 'sql'
| 'graphql'
| 'powershell'
| 'php'
| 'css'
| 'javascript'
export let code: string = ''
@@ -166,6 +168,12 @@
}
}
export function backspace(): void {
if (editor) {
editor.trigger('keyboard', 'deleteLeft', {})
}
}
export function insertAtBeginning(code: string): void {
if (editor) {
const range = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }
+65 -7
View File
@@ -86,11 +86,19 @@
'go',
'deno',
'bun',
'nativets'
'nativets',
'php'
].includes(lang ?? '')
$: showVarPicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'nativets',
'php'
].includes(lang ?? '')
$: showVarPicker = ['python3', 'bash', 'powershell', 'go', 'deno', 'bun', 'nativets'].includes(
lang ?? ''
)
$: showResourcePicker = [
'python3',
'bash',
@@ -98,10 +106,13 @@
'go',
'deno',
'bun',
'nativets'
'nativets',
'php'
].includes(lang ?? '')
$: showResourceTypePicker =
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) || lang === 'python3'
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
lang === 'python3' ||
lang === 'php'
let codeViewer: Drawer
let codeObj: { language: SupportedLanguage; content: string } | undefined = undefined
@@ -187,12 +198,47 @@
if (!code.includes('from typing import TypedDict')) {
editor.insertAtBeginning('from typing import TypedDict\n')
}
} else if (lang === 'php') {
const phpSchema = phpCompile(resourceType.schema as any)
const rtName = toCamel(capitalize(name))
editor.insertAtCursor(`if (!class_exists('${rtName}')) {\nclass ${rtName} {\n${phpSchema}\n`)
editor.backspace()
editor.insertAtCursor('}')
} else {
const tsSchema = compile(resourceType.schema as any)
editor.insertAtCursor(`type ${toCamel(capitalize(name))} = ${tsSchema}\n`)
editor.insertAtCursor(`type ${toCamel(capitalize(name))} = ${tsSchema}`)
}
sendUserToast(`${name} inserted at cursor`)
}
function phpCompile(schema: Schema) {
let res = ' '
const entries = Object.entries(schema.properties)
if (entries.length === 0) {
return 'array'
}
let i = 0
for (let [name, prop] of entries) {
let typ = 'array'
if (prop.type === 'array') {
typ = 'array'
} else if (prop.type === 'string') {
typ = 'string'
} else if (prop.type === 'number') {
typ = 'float'
} else if (prop.type === 'integer') {
typ = 'int'
} else if (prop.type === 'boolean') {
typ = 'bool'
}
res += `public ${typ} $${name};`
i++
if (i < entries.length) {
res += '\n'
}
}
return res
}
function pythonCompile(schema: Schema) {
let res = ''
const entries = Object.entries(schema.properties)
@@ -285,6 +331,8 @@
editor.insertAtCursor(`$${name}`)
} else if (lang == 'powershell') {
editor.insertAtCursor(`$Env:${name}`)
} else if (lang == 'php') {
editor.insertAtCursor(`getenv('${name}');`)
}
sendUserToast(`${name} inserted at cursor`)
}}
@@ -336,6 +384,11 @@
editor.insertAtBeginning(`import * as wmill from "./windmill.ts"\n`)
}
editor.insertAtCursor(`(await wmill.getVariable('${path}'))`)
} else if (lang == 'php') {
editor.insertAtCursor(`$ch = curl_init(getenv('BASE_INTERNAL_URL') . '/api/w/' . getenv('WM_WORKSPACE') . '/variables/get_value/${path}');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . getenv('WM_TOKEN')));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$var = json_decode(curl_exec($ch));`)
}
sendUserToast(`${name} inserted at cursor`)
}}
@@ -401,6 +454,11 @@
editor.insertAtBeginning(`import * as wmill from "./windmill.ts"\n`)
}
editor.insertAtCursor(`(await wmill.getResource('${path}'))`)
} else if (lang == 'php') {
editor.insertAtCursor(`$ch = curl_init(getenv('BASE_INTERNAL_URL') . '/api/w/' . getenv('WM_WORKSPACE') . '/resources/get_value_interpolated/${path}');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . getenv('WM_TOKEN')));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$res = json_decode(curl_exec($ch));`)
}
sendUserToast(`${path} inserted at cursor`)
}}
@@ -8,6 +8,7 @@
import javascript from 'svelte-highlight/languages/javascript'
import sql from 'svelte-highlight/languages/sql'
import powershell from 'svelte-highlight/languages/powershell'
import php from 'svelte-highlight/languages/php'
import type { Script } from '$lib/gen'
import { Button } from './common'
import { copyToClipboard } from '$lib/utils'
@@ -45,6 +46,8 @@
return sql
case 'powershell':
return powershell
case 'php':
return php
default:
return typescript
@@ -93,7 +93,8 @@
'flow',
'hub',
'other',
'bun'
'bun',
'php'
]
const nativeTags = [
'nativets',
@@ -15,6 +15,7 @@
import BunIcon from '$lib/components/icons/BunIcon.svelte'
import DenoIcon from '$lib/components/icons/DenoIcon.svelte'
import type { Script } from '$lib/gen'
import PHPIcon from '$lib/components/icons/PHPIcon.svelte'
export let lang:
| SupportedLanguage
@@ -42,7 +43,8 @@
snowflake: 'Snowflake',
mysql: 'MySQL',
mssql: 'MS SQL Server',
bun: 'TypeScript'
bun: 'TypeScript',
php: 'PHP'
}
const langToComponent: Record<
@@ -66,7 +68,8 @@
powershell: PowershellIcon,
postgresql: PostgresIcon,
nativets: RestIcon,
graphql: GraphqlIcon
graphql: GraphqlIcon,
php: PHPIcon
}
let subIconScale = width === 30 ? 0.6 : 0.8
@@ -0,0 +1,118 @@
<script lang="ts">
export let height = '24px'
export let width = '24px'
</script>
<svg
{height}
{width}
id="svg3430"
version="1.1"
viewBox="0 0 711.20123 383.5975"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
>
<title id="title3510">Official PHP Logo</title>
<defs id="defs3434">
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath3444">
<path
d="M 11.52,162 C 11.52,81.677 135.307,16.561 288,16.561 l 0,0 c 152.693,0 276.481,65.116 276.481,145.439 l 0,0 c 0,80.322 -123.788,145.439 -276.481,145.439 l 0,0 C 135.307,307.439 11.52,242.322 11.52,162"
id="path3446"
/>
</clipPath>
<radialGradient
cx="0"
cy="0"
fx="0"
fy="0"
gradientTransform="matrix(363.05789,0,0,-363.05789,177.52002,256.30713)"
gradientUnits="userSpaceOnUse"
id="radialGradient3452"
r="1"
spreadMethod="pad"
>
<stop id="stop3454" offset="0" style="stop-opacity:1;stop-color:#aeb2d5" />
<stop id="stop3456" offset="0.3" style="stop-opacity:1;stop-color:#aeb2d5" />
<stop id="stop3458" offset="0.75" style="stop-opacity:1;stop-color:#484c89" />
<stop id="stop3460" offset="1" style="stop-opacity:1;stop-color:#484c89" />
</radialGradient>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath3468">
<path d="M 0,324 576,324 576,0 0,0 0,324 Z" id="path3470" />
</clipPath>
<clipPath clipPathUnits="userSpaceOnUse" id="clipPath3480">
<path d="M 0,324 576,324 576,0 0,0 0,324 Z" id="path3482" />
</clipPath>
</defs>
<g id="g3438" transform="matrix(1.25,0,0,-1.25,-4.4,394.29875)">
<g id="g3440">
<g clip-path="url(#clipPath3444)" id="g3442">
<g id="g3448">
<g id="g3450">
<path
d="M 11.52,162 C 11.52,81.677 135.307,16.561 288,16.561 l 0,0 c 152.693,0 276.481,65.116 276.481,145.439 l 0,0 c 0,80.322 -123.788,145.439 -276.481,145.439 l 0,0 C 135.307,307.439 11.52,242.322 11.52,162"
id="path3462"
style="fill:url(#radialGradient3452);stroke:none"
/>
</g>
</g>
</g>
</g>
<g id="g3464">
<g clip-path="url(#clipPath3468)" id="g3466">
<g id="g3472" transform="translate(288,27.3594)">
<path
d="M 0,0 C 146.729,0 265.68,60.281 265.68,134.641 265.68,209 146.729,269.282 0,269.282 -146.729,269.282 -265.68,209 -265.68,134.641 -265.68,60.281 -146.729,0 0,0"
id="path3474"
style="fill:#777bb3;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
</g>
</g>
<g id="g3476">
<g clip-path="url(#clipPath3480)" id="g3478">
<g id="g3484" transform="translate(161.7344,145.3066)">
<path
d="m 0,0 c 12.065,0 21.072,2.225 26.771,6.611 5.638,4.341 9.532,11.862 11.573,22.353 1.903,9.806 1.178,16.653 -2.154,20.348 C 32.783,53.086 25.417,55 14.297,55 L -4.984,55 -15.673,0 0,0 Z m -63.063,-67.75 c -0.895,0 -1.745,0.4 -2.314,1.092 -0.57,0.691 -0.801,1.601 -0.63,2.48 L -37.679,81.573 C -37.405,82.982 -36.17,84 -34.734,84 L 26.32,84 C 45.508,84 59.79,78.79 68.767,68.513 77.792,58.182 80.579,43.741 77.05,25.592 75.614,18.198 73.144,11.331 69.709,5.183 66.27,-0.972 61.725,-6.667 56.198,-11.747 49.582,-17.939 42.094,-22.429 33.962,-25.071 25.959,-27.678 15.681,-29 3.414,-29 l -24.722,0 -7.06,-36.322 c -0.274,-1.41 -1.508,-2.428 -2.944,-2.428 l -31.751,0 z"
id="path3486"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
<g id="g3488" transform="translate(159.2236,197.3071)">
<path
d="m 0,0 16.808,0 c 13.421,0 18.083,-2.945 19.667,-4.7 2.628,-2.914 3.124,-9.058 1.435,-17.767 C 36.012,-32.217 32.494,-39.13 27.452,-43.012 22.29,-46.986 13.898,-49 2.511,-49 L -9.523,-49 0,0 Z m 28.831,35 -61.055,0 c -2.872,0 -5.341,-2.036 -5.889,-4.855 l -28.328,-145.751 c -0.342,-1.759 0.12,-3.578 1.259,-4.961 1.14,-1.383 2.838,-2.183 4.63,-2.183 l 31.75,0 c 2.873,0 5.342,2.036 5.89,4.855 l 6.588,33.895 22.249,0 c 12.582,0 23.174,1.372 31.479,4.077 8.541,2.775 16.399,7.48 23.354,13.984 5.752,5.292 10.49,11.232 14.08,17.657 3.591,6.427 6.171,13.594 7.668,21.302 3.715,19.104 0.697,34.402 -8.969,45.466 C 63.965,29.444 48.923,35 28.831,35 m -45.633,-90 19.313,0 c 12.801,0 22.336,2.411 28.601,7.234 6.266,4.824 10.492,12.875 12.688,24.157 2.101,10.832 1.144,18.476 -2.871,22.929 C 36.909,3.773 28.87,6 16.808,6 L -4.946,6 -16.802,-55 M 28.831,29 C 47.198,29 60.597,24.18 69.019,14.539 77.44,4.898 79.976,-8.559 76.616,-25.836 75.233,-32.953 72.894,-39.46 69.601,-45.355 66.304,-51.254 61.999,-56.648 56.679,-61.539 50.339,-67.472 43.296,-71.7 35.546,-74.218 27.796,-76.743 17.925,-78 5.925,-78 l -27.196,0 -7.531,-38.75 -31.75,0 28.328,145.75 61.055,0"
id="path3490"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
<g id="g3492" transform="translate(311.583,116.3066)">
<path
d="m 0,0 c -0.896,0 -1.745,0.4 -2.314,1.092 -0.571,0.691 -0.802,1.6 -0.631,2.48 L 9.586,68.061 C 10.778,74.194 10.484,78.596 8.759,80.456 7.703,81.593 4.531,83.5 -4.848,83.5 L -27.55,83.5 -43.305,2.428 C -43.579,1.018 -44.814,0 -46.25,0 l -31.5,0 c -0.896,0 -1.745,0.4 -2.315,1.092 -0.57,0.691 -0.801,1.601 -0.63,2.48 l 28.328,145.751 c 0.274,1.409 1.509,2.427 2.945,2.427 l 31.5,0 c 0.896,0 1.745,-0.4 2.315,-1.091 0.57,-0.692 0.801,-1.601 0.63,-2.481 L -21.813,113 2.609,113 c 18.605,0 31.221,-3.28 38.569,-10.028 7.49,-6.884 9.827,-17.891 6.947,-32.719 L 34.945,2.428 C 34.671,1.018 33.437,0 32,0 L 0,0 Z"
id="path3494"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
<g id="g3496" transform="translate(293.6611,271.0571)">
<path
d="m 0,0 -31.5,0 c -2.873,0 -5.342,-2.036 -5.89,-4.855 l -28.328,-145.751 c -0.342,-1.759 0.12,-3.578 1.26,-4.961 1.14,-1.383 2.838,-2.183 4.63,-2.183 l 31.5,0 c 2.872,0 5.342,2.036 5.89,4.855 l 15.283,78.645 20.229,0 c 9.363,0 11.328,-2 11.407,-2.086 0.568,-0.611 1.315,-3.441 0.082,-9.781 l -12.531,-64.489 c -0.342,-1.759 0.12,-3.578 1.26,-4.961 1.14,-1.383 2.838,-2.183 4.63,-2.183 l 32,0 c 2.872,0 5.342,2.036 5.89,4.855 l 13.179,67.825 c 3.093,15.921 0.447,27.864 -7.861,35.5 -7.928,7.281 -21.208,10.82 -40.599,10.82 l -20.784,0 6.143,31.605 C 6.231,-5.386 5.77,-3.566 4.63,-2.184 3.49,-0.801 1.792,0 0,0 m 0,-6 -7.531,-38.75 28.062,0 c 17.657,0 29.836,-3.082 36.539,-9.238 6.703,-6.16 8.711,-16.141 6.032,-29.938 l -13.18,-67.824 -32,0 12.531,64.488 c 1.426,7.336 0.902,12.34 -1.574,15.008 -2.477,2.668 -7.746,4.004 -15.805,4.004 l -25.176,0 -16.226,-83.5 -31.5,0 L -31.5,-6 0,-6"
id="path3498"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
<g id="g3500" transform="translate(409.5498,145.3066)">
<path
d="m 0,0 c 12.065,0 21.072,2.225 26.771,6.611 5.638,4.34 9.532,11.861 11.574,22.353 1.903,9.806 1.178,16.653 -2.155,20.348 C 32.783,53.086 25.417,55 14.297,55 L -4.984,55 -15.673,0 0,0 Z m -63.062,-67.75 c -0.895,0 -1.745,0.4 -2.314,1.092 -0.57,0.691 -0.802,1.601 -0.631,2.48 L -37.679,81.573 C -37.404,82.982 -36.17,84 -34.733,84 L 26.32,84 C 45.509,84 59.79,78.79 68.768,68.513 77.793,58.183 80.579,43.742 77.051,25.592 75.613,18.198 73.144,11.331 69.709,5.183 66.27,-0.972 61.725,-6.667 56.198,-11.747 49.582,-17.939 42.094,-22.429 33.962,-25.071 25.959,-27.678 15.681,-29 3.414,-29 l -24.723,0 -7.057,-36.322 c -0.275,-1.41 -1.509,-2.428 -2.946,-2.428 l -31.75,0 z"
id="path3502"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
<g id="g3504" transform="translate(407.0391,197.3071)">
<path
d="M 0,0 16.808,0 C 30.229,0 34.891,-2.945 36.475,-4.7 39.104,-7.614 39.6,-13.758 37.91,-22.466 36.012,-32.217 32.493,-39.13 27.452,-43.012 22.29,-46.986 13.898,-49 2.511,-49 L -9.522,-49 0,0 Z m 28.831,35 -61.054,0 c -2.872,0 -5.341,-2.036 -5.889,-4.855 L -66.44,-115.606 c -0.342,-1.759 0.12,-3.578 1.259,-4.961 1.14,-1.383 2.838,-2.183 4.63,-2.183 l 31.75,0 c 2.872,0 5.342,2.036 5.89,4.855 l 6.587,33.895 22.249,0 c 12.582,0 23.174,1.372 31.479,4.077 8.541,2.775 16.401,7.481 23.356,13.986 5.752,5.291 10.488,11.23 14.078,17.655 3.591,6.427 6.171,13.594 7.668,21.302 3.715,19.105 0.697,34.403 -8.969,45.467 C 63.965,29.444 48.924,35 28.831,35 m -45.632,-90 19.312,0 c 12.801,0 22.336,2.411 28.601,7.234 6.267,4.824 10.492,12.875 12.688,24.157 2.102,10.832 1.145,18.476 -2.871,22.929 C 36.909,3.773 28.87,6 16.808,6 L -4.946,6 -16.801,-55 M 28.831,29 C 47.198,29 60.597,24.18 69.019,14.539 77.441,4.898 79.976,-8.559 76.616,-25.836 75.233,-32.953 72.894,-39.46 69.601,-45.355 66.304,-51.254 61.999,-56.648 56.679,-61.539 50.339,-67.472 43.296,-71.7 35.546,-74.218 27.796,-76.743 17.925,-78 5.925,-78 l -27.196,0 -7.53,-38.75 -31.75,0 28.328,145.75 61.054,0"
id="path3506"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
/>
</g>
</g>
</g>
</g>
</svg>
+2
View File
@@ -58,6 +58,8 @@ export function langToExt(lang: string): string {
return 'sh'
case 'powershell':
return 'ps1'
case 'php':
return 'php'
case 'deno':
return 'ts'
case 'nativets':
+4 -1
View File
@@ -17,7 +17,8 @@ import init, {
parse_outputs,
parse_mssql,
parse_ts_imports,
parse_db_resource
parse_db_resource,
parse_php
} from 'windmill-parser-wasm'
import wasmUrl from 'windmill-parser-wasm/windmill_parser_wasm_bg.wasm?url'
import { workspaceStore } from './stores.js'
@@ -120,6 +121,8 @@ export async function inferArgs(
inferedSchema = JSON.parse(parse_bash(code))
} else if (language == 'powershell') {
inferedSchema = JSON.parse(parse_powershell(code))
} else if (language == 'php') {
inferedSchema = JSON.parse(parse_php(code))
} else {
return
}
+24 -1
View File
@@ -190,6 +190,26 @@ export const GRAPHQL_INIT_CODE = `query($name4: String, $name2: Int, $name3: [St
}
`
export const PHP_INIT_CODE = `<?php
// remove the first // of the following lines to specify packages to install using composer
// // require:
// // monolog/monolog@3.6.0
// // stripe/stripe-php
function main(
// Postgresql $a,
// array $b,
// object $c,
int $d = 123,
string $e = "default value",
float $f = 3.5,
bool $g = true,
) {
return $d;
}
`
export const FETCH_INIT_CODE = `export async function main(
url: string | undefined,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
@@ -378,7 +398,8 @@ const ALL_INITIAL_CODE = [
BUN_INIT_CODE_CLEAR,
BUN_INIT_CODE_APPROVAL,
BASH_INIT_CODE,
POWERSHELL_INIT_CODE
POWERSHELL_INIT_CODE,
PHP_INIT_CODE
]
export function isInitialCode(content: string): boolean {
@@ -454,6 +475,8 @@ export function initialCode(
return MSSQL_INIT_CODE
} else if (language == 'graphql') {
return GRAPHQL_INIT_CODE
} else if (language == 'php') {
return PHP_INIT_CODE
} else if (language == 'bun') {
if (kind === 'approval') {
return BUN_INIT_CODE_APPROVAL
+3
View File
@@ -28,6 +28,8 @@ export function scriptLangToEditorLang(lang: Script['language'] | undefined) {
return 'shell'
} else if (lang == 'powershell') {
return 'powershell'
} else if (lang == 'php') {
return 'php'
} else if (lang == 'graphql') {
return 'graphql'
} else if (lang == undefined) {
@@ -103,6 +105,7 @@ const scriptLanguagesArray: [SupportedLanguage | 'docker', string][] = [
['mssql', 'MS SQL Server'],
['graphql', 'GraphQL'],
['powershell', 'PowerShell'],
['php', 'PHP'],
['docker', 'Docker']
]
export const defaultScriptLanguages = Object.fromEntries(scriptLanguagesArray)
@@ -628,7 +628,7 @@
<Badge color="blue">priority: {job.priority}</Badge>
</div>
{/if}
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'other', 'dependency'].includes(job.tag)}
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'php', 'other', 'dependency'].includes(job.tag)}
<div>
<Badge color="indigo">Tag: {job.tag}</Badge>
</div>