diff --git a/backend/migrations/20240729064122_addbunnative.down.sql b/backend/migrations/20240729064122_addbunnative.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20240729064122_addbunnative.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20240729064122_addbunnative.up.sql b/backend/migrations/20240729064122_addbunnative.up.sql new file mode 100644 index 0000000000..d3e5f61cc3 --- /dev/null +++ b/backend/migrations/20240729064122_addbunnative.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'bunnative'; \ No newline at end of file diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index a274fcb3c1..1aabeac6e9 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -52,7 +52,7 @@ use windmill_common::{ utils::{ not_found_if_none, paginate, query_elems_from_hub, require_admin, Pagination, StripPath, }, - worker::to_raw_value, + worker::{get_annotation, to_raw_value}, HUB_BASE_URL, }; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; @@ -585,6 +585,17 @@ async fn create_script_internal<'c>( } else { envs }; + + let lang = if &ns.language == &ScriptLang::Bun || &ns.language == &ScriptLang::Bunnative { + let anns = get_annotation(&ns.content); + if anns.native_mode { + ScriptLang::Bunnative + } else { + ScriptLang::Bun + } + } else { + ns.language.clone() + }; sqlx::query!( "INSERT INTO script (workspace_id, hash, path, parent_hashes, summary, description, \ content, created_by, schema, is_template, extra_perms, lock, language, kind, tag, \ @@ -604,7 +615,7 @@ async fn create_script_internal<'c>( ns.is_template.unwrap_or(false), extra_perms, lock, - ns.language.clone() as ScriptLang, + lang as ScriptLang, ns.kind.unwrap_or(ScriptKind::Script) as ScriptKind, ns.tag, ns.draft_only, diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 360ff3bfbe..c9e511f892 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -2498,7 +2498,7 @@ async fn tarball_workspace( ScriptLang::Mssql => "ms.sql", ScriptLang::Graphql => "gql", ScriptLang::Nativets => "fetch.ts", - ScriptLang::Bun => { + ScriptLang::Bun | ScriptLang::Bunnative => { if default_ts.as_ref().is_some_and(|x| x == "bun") { "ts" } else { diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index a88d357667..bee669031b 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -34,6 +34,7 @@ pub enum ScriptLang { Powershell, Postgresql, Bun, + Bunnative, Mysql, Bigquery, Snowflake, @@ -46,6 +47,7 @@ impl ScriptLang { pub fn as_str(&self) -> &'static str { match self { ScriptLang::Bun => "bun", + ScriptLang::Bunnative => "bunnative", ScriptLang::Nativets => "nativets", ScriptLang::Deno => "deno", ScriptLang::Python3 => "python3", diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index d743276cdb..2185cf0273 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -3702,10 +3702,15 @@ pub async fn push<'c, 'd, R: rsmq_async::RsmqConnection + Send + 'c>( language .as_ref() .map(|x| { - if per_workspace { - format!("{}-{}", x.as_str(), workspace_id) + let tag_lang = if x == &ScriptLang::Bunnative { + ScriptLang::Nativets.as_str() } else { - x.as_str().to_string() + x.as_str() + }; + if per_workspace { + format!("{}-{}", tag_lang, workspace_id) + } else { + tag_lang.to_string() } }) .unwrap_or_else(default) diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 1d666ffdcf..b5851af2c1 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -34,7 +34,9 @@ use windmill_common::variables; use windmill_common::{ error::{self, Result}, + get_latest_hash_for_path, jobs::QueuedJob, + scripts::ScriptLang, worker::{exists_in_cache, get_annotation, save_cache}, DB, }; @@ -577,7 +579,8 @@ pub async fn prebundle_script( worker_name: &str, token: &str, ) -> Result<()> { - let (local_path, remote_path) = compute_bundle_local_and_remote_path(inner_content, &lockfile); + let (local_path, remote_path) = + compute_bundle_local_and_remote_path(inner_content, &lockfile, script_path, db, w_id).await; if exists_in_cache(&local_path, &remote_path).await { return Ok(()); } @@ -622,18 +625,48 @@ pub async fn prebundle_script( pub const BUN_BUNDLE_OBJECT_STORE_PREFIX: &str = "bun_bundle/"; -fn compute_bundle_local_and_remote_path( +async fn get_script_import_updated_at(db: &DB, w_id: &str, script_path: &str) -> Result { + let script_hash = get_latest_hash_for_path(&mut db.begin().await?, w_id, script_path).await?; + let last_updated_at = sqlx::query_scalar!( + "SELECT created_at FROM script WHERE workspace_id = $1 AND hash = $2", + w_id, + script_hash.0 .0 + ) + .fetch_one(db) + .await?; + Ok(last_updated_at.to_string()) +} + +async fn compute_bundle_local_and_remote_path( inner_content: &str, requirements_o: &Option, + script_path: &str, + db: &DB, + w_id: &str, ) -> (String, String) { - let hash = windmill_common::utils::calculate_hash(&format!( + let mut input_src = format!( "{}{}", inner_content, requirements_o .as_ref() .map(|x| x.to_string()) .unwrap_or_default() - )); + ); + + let relative_imports = crate::worker_lockfiles::extract_relative_imports( + &inner_content, + script_path, + &Some(ScriptLang::Bun), + ); + + for path in relative_imports.unwrap_or_default() { + if let Ok(updated_at) = get_script_import_updated_at(db, w_id, &path).await { + input_src.push_str(&path); + input_src.push_str(&updated_at.to_string()); + } + } + + let hash = windmill_common::utils::calculate_hash(&input_src); let local_path = format!("{BUN_BUNDLE_CACHE_DIR}/{hash}"); let remote_path = format!("{BUN_BUNDLE_OBJECT_STORE_PREFIX}{hash}"); (local_path, remote_path) @@ -657,10 +690,17 @@ pub async fn handle_bun_job( ) -> error::Result> { let mut annotation = windmill_common::worker::get_annotation(inner_content); - let (mut bundle_cache, cache_logs, local_path, remote_path) = + let (mut has_bundle_cache, cache_logs, local_path, remote_path) = if requirements_o.is_some() && !annotation.nobundling && codebase.is_none() { - let (local_path, remote_path) = - compute_bundle_local_and_remote_path(inner_content, &requirements_o); + let (local_path, remote_path) = compute_bundle_local_and_remote_path( + inner_content, + &requirements_o, + job.script_path(), + db, + &job.workspace_id, + ) + .await; + let (cache, logs) = windmill_common::worker::load_cache(&local_path, &remote_path).await; (cache, logs, local_path, remote_path) @@ -668,9 +708,9 @@ pub async fn handle_bun_job( (false, "".to_string(), "".to_string(), "".to_string()) }; - if !codebase.is_some() && !bundle_cache { + if !codebase.is_some() && !has_bundle_cache { let _ = write_file(job_dir, "main.ts", inner_content).await?; - } else { + } else if !annotation.native_mode { let _ = write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?; }; @@ -690,7 +730,7 @@ pub async fn handle_bun_job( } let mut gbuntar_name = None; - if bundle_cache { + if has_bundle_cache { let target = format!("{job_dir}/main.js"); std::os::unix::fs::symlink(&local_path, &target).map_err(|e| { error::Error::ExecutionErr(format!( @@ -807,7 +847,7 @@ pub async fn handle_bun_job( let mut init_logs = if annotation.native_mode { "\n\n--- NATIVE CODE EXECUTION ---\n".to_string() - } else if bundle_cache { + } else if has_bundle_cache { if annotation.nodejs_mode { "\n\n--- NODE BUNDLE SNAPSHOT EXECUTION ---\n".to_string() } else { @@ -832,11 +872,14 @@ pub async fn handle_bun_job( ); } - if bundle_cache { + if has_bundle_cache { init_logs = format!("\n{}{}", cache_logs, init_logs); } let write_wrapper_f = async { + if !has_bundle_cache && annotation.native_mode { + return Ok(()) as error::Result<()>; + } // let mut start = Instant::now(); let args = windmill_parser_ts::parse_deno_signature(inner_content, true, main_override.clone())? @@ -859,7 +902,7 @@ pub async fn handle_bun_job( // we cannot use Bun.read and Bun.write because it results in an EBADF error on cloud let main_name = main_override.unwrap_or("main".to_string()); - let main_import = if codebase.is_some() || bundle_cache { + let main_import = if codebase.is_some() || has_bundle_cache { "./main.js" } else { "./main.ts" @@ -904,6 +947,9 @@ try {{ }; let reserved_variables_args_out_f = async { + if annotation.native_mode { + return Ok(HashMap::new()) as error::Result>; + } let args_and_out_f = async { create_args_and_out_file(&client, job, job_dir, db).await?; Ok(()) as Result<()> @@ -917,7 +963,7 @@ try {{ Ok(reserved_variables) as error::Result> }; - let build_cache = !bundle_cache + let build_cache = !has_bundle_cache && !annotation.nobundling && !codebase.is_some() && (requirements_o.is_some() || annotation.native_mode); @@ -939,7 +985,7 @@ try {{ .await?; Ok(()) - } else if !codebase.is_some() && !bundle_cache { + } else if !codebase.is_some() && !has_bundle_cache { build_loader( job_dir, base_internal_url, @@ -963,7 +1009,7 @@ try {{ write_wrapper_f, write_loader_f )?; - if !codebase.is_some() && !bundle_cache { + if !codebase.is_some() && !has_bundle_cache { if build_cache { generate_bun_bundle( job_dir, @@ -977,7 +1023,6 @@ try {{ &common_bun_proc_envs, ) .await?; - if !local_path.is_empty() { match save_cache(&local_path, &remote_path, &format!("{job_dir}/main.js")).await { Err(e) => { @@ -992,19 +1037,21 @@ try {{ } } } - let ex_wrapper = read_file_content(&format!("{job_dir}/wrapper.mjs")).await?; - write_file( - job_dir, - "wrapper.mjs", - &ex_wrapper.replace( - "import * as Main from \"./main.ts\"", - "import * as Main from \"./main.js\"", - ), - ) - .await?; - write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?; + if !annotation.native_mode { + let ex_wrapper = read_file_content(&format!("{job_dir}/wrapper.mjs")).await?; + write_file( + job_dir, + "wrapper.mjs", + &ex_wrapper.replace( + "import * as Main from \"./main.ts\"", + "import * as Main from \"./main.js\"", + ), + ) + .await?; + write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?; + } fs::remove_file(format!("{job_dir}/main.ts"))?; - bundle_cache = true; + has_bundle_cache = true; } else if annotation.nodejs_mode { generate_wrapper_mjs( job_dir, @@ -1100,7 +1147,7 @@ try {{ &NODE_PATH, "/tmp/nodejs/wrapper.mjs", ] - } else if codebase.is_some() || bundle_cache { + } else if codebase.is_some() || has_bundle_cache { vec![ "--config", "run.config.proto", @@ -1154,7 +1201,7 @@ try {{ let script_path = format!("{job_dir}/wrapper.mjs"); let mut bun_cmd = Command::new(&*BUN_PATH); - let args = if codebase.is_some() || bundle_cache { + let args = if codebase.is_some() || has_bundle_cache { vec!["run", &script_path] } else { vec![ diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 78435d070c..fd8d386ce8 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -3023,7 +3023,7 @@ mount {{ ) .await } - Some(ScriptLang::Bun) => { + Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => { handle_bun_job( requirements_o, codebase, diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 051d8430a8..092e044ec6 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -12,7 +12,7 @@ use windmill_common::flows::{FlowModule, FlowModuleValue}; use windmill_common::get_latest_deployed_hash_for_path; use windmill_common::jobs::JobPayload; use windmill_common::scripts::ScriptHash; -use windmill_common::worker::{to_raw_value, to_raw_value_owned}; +use windmill_common::worker::{get_annotation, to_raw_value, to_raw_value_owned}; use windmill_common::{ error::{self, to_anyhow}, flows::FlowValue, @@ -187,14 +187,16 @@ fn parse_bun_relative_imports(raw_code: &str, script_path: &str) -> error::Resul Ok(relative_imports) } -fn extract_relative_imports( +pub fn extract_relative_imports( raw_code: &str, script_path: &str, language: &Option, ) -> Option> { match language { Some(ScriptLang::Python3) => parse_relative_imports(&raw_code, script_path).ok(), - Some(ScriptLang::Bun) => parse_bun_relative_imports(&raw_code, script_path).ok(), + Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => { + parse_bun_relative_imports(&raw_code, script_path).ok() + } _ => None, } } @@ -286,7 +288,7 @@ pub async fn handle_dependency_job( lock, path, content, - language, + mut language, input_transforms, tag, custom_concurrency_key, @@ -852,8 +854,11 @@ async fn lock_modules<'c>( } } else { if lock.as_ref().is_some_and(|x| !x.trim().is_empty()) { - new_flow_modules.push(e); - continue; + let skip_creating_new_lock = skip_creating_new_lock(&language, &content); + if skip_creating_new_lock { + new_flow_modules.push(e); + continue; + } } } @@ -911,6 +916,14 @@ async fn lock_modules<'c>( append_logs(&job.id, &job.workspace_id, logs, db).await; } + if language == ScriptLang::Bun || language == ScriptLang::Bunnative { + let anns = get_annotation(&content); + if anns.native_mode && language == ScriptLang::Bun { + language = ScriptLang::Bunnative; + } else if !anns.native_mode && language == ScriptLang::Bunnative { + language = ScriptLang::Bun; + }; + } e.value = windmill_common::worker::to_raw_value(&FlowModuleValue::RawScript { lock: Some(new_lock), path, @@ -952,6 +965,18 @@ async fn lock_modules<'c>( Ok((new_flow_modules, tx, modified_ids)) } +fn skip_creating_new_lock(language: &ScriptLang, content: &str) -> bool { + if language == &ScriptLang::Bun || language == &ScriptLang::Bunnative { + let anns = get_annotation(&content); + if anns.native_mode && language == &ScriptLang::Bun { + return false; + } else if !anns.native_mode && language == &ScriptLang::Bunnative { + return false; + }; + } + true +} + #[async_recursion] async fn lock_modules_app( value: Value, @@ -985,10 +1010,12 @@ async fn lock_modules_app( if v.get("lock") .is_some_and(|x| !x.as_str().unwrap().trim().is_empty()) { - logs.push_str( - "Found already locked inline script. Skipping lock...\n", - ); - return Ok(Value::Object(m.clone())); + if skip_creating_new_lock(&language, &content) { + logs.push_str( + "Found already locked inline script. Skipping lock...\n", + ); + return Ok(Value::Object(m.clone())); + } } logs.push_str("Found lockable inline script. Generating lock...\n"); let new_lock = capture_dependency_job( @@ -1012,6 +1039,21 @@ async fn lock_modules_app( match new_lock { Ok(new_lock) => { append_logs(&job.id, &job.workspace_id, logs, db).await; + let anns = get_annotation(&content); + let nlang = if anns.native_mode && language == ScriptLang::Bun { + Some(ScriptLang::Bunnative) + } else if !anns.native_mode && language == ScriptLang::Bunnative + { + Some(ScriptLang::Bun) + } else { + None + }; + if let Some(nlang) = nlang { + v.insert( + "language".to_string(), + serde_json::Value::String(nlang.as_str().to_string()), + ); + } v.insert( "lock".to_string(), serde_json::Value::String(new_lock), @@ -1293,7 +1335,7 @@ async fn capture_dependency_job( ) .await } - ScriptLang::Bun => { + ScriptLang::Bun | ScriptLang::Bunnative => { let npm_mode = npm_mode .unwrap_or_else(|| windmill_common::worker::get_annotation(job_raw_code).npm_mode); if !raw_deps { diff --git a/frontend/src/lib/components/DefaultScriptsInner.svelte b/frontend/src/lib/components/DefaultScriptsInner.svelte index fa76183827..154732e5d0 100644 --- a/frontend/src/lib/components/DefaultScriptsInner.svelte +++ b/frontend/src/lib/components/DefaultScriptsInner.svelte @@ -11,7 +11,9 @@ function computeLangs(defaultScripts: WorkspaceDefaultScripts | undefined) { const allLangs = Object.keys(defaultScriptLanguages) if (!defaultScripts || defaultScripts.order == undefined) return allLangs - return defaultScripts.order?.concat(allLangs.filter((l) => !defaultScripts.order?.includes(l))) + return defaultScripts.order + ?.concat(allLangs.filter((l) => !defaultScripts.order?.includes(l))) + .filter((x) => x != 'nativets') } async function changePosition(i: number, up: boolean) { diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index bd801b15a7..733cb6dbcc 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -111,7 +111,7 @@ export let useWebsockets: boolean = true export let listenEmptyChanges = false export let small = false - export let scriptLang: Preview['language'] + export let scriptLang: Preview['language'] | 'bunnative' export let disabled: boolean = false const rHash = randomHash() @@ -127,7 +127,9 @@ let initialPath: string | undefined = path - $: path != initialPath && (scriptLang == 'deno' || scriptLang == 'bun') && handlePathChange() + $: path != initialPath && + (scriptLang == 'deno' || scriptLang == 'bun' || scriptLang == 'bunnative') && + handlePathChange() let websockets: WebSocket[] = [] let languageClients: MonacoLanguageClient[] = [] @@ -1147,7 +1149,7 @@ const hostname = getHostname() // const stdLib = { content: libStdContent, filePath: 'es6.d.ts' } - if (scriptLang == 'bun') { + if (scriptLang == 'bun' || scriptLang == 'bunnative') { // const processLib = { content: processStdContent, filePath: 'process.d.ts' } // const domLib = { content: domContent, filePath: 'dom.d.ts' } // languages.typescript.typescriptDefaults.setExtraLibs([stdLib, domLib, processLib]) diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index a3111ea283..50a59765db 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -58,7 +58,8 @@ export let iconOnly: boolean = false export let validCode: boolean = true export let kind: 'script' | 'trigger' | 'approval' = 'script' - export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' = 'script' + export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' = + 'script' export let collabMode = false export let collabLive = false export let collabUsers: { name: string }[] = [] diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 25bec77170..30de354688 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -57,7 +57,12 @@ import MetadataGen from './copilot/MetadataGen.svelte' import ScriptSchedules from './ScriptSchedules.svelte' import { writable } from 'svelte/store' - import { type ScriptSchedule, loadScriptSchedule, defaultScriptLanguages } from '$lib/scripts' + import { + type ScriptSchedule, + loadScriptSchedule, + defaultScriptLanguages, + processLangs + } from '$lib/scripts' import DefaultScripts from './DefaultScripts.svelte' import { createEventDispatcher } from 'svelte' import CustomPopover from './CustomPopover.svelte' @@ -65,7 +70,7 @@ export let script: NewScript export let initialPath: string = '' - export let template: 'docker' | 'script' = 'script' + export let template: 'docker' | 'bunnative' | 'script' = 'script' export let initialArgs: Record = {} export let lockedLanguage = false export let showMeta: boolean = false @@ -114,11 +119,14 @@ editor?.setCode(code) } - $: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages)) + $: langs = processLangs( + script.language, + $defaultScripts?.order ?? Object.keys(defaultScriptLanguages) + ) .map((l) => [defaultScriptLanguages[l], l]) .filter( (x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]) - ) as [string, SupportedLanguage | 'docker'][] + ) as [string, SupportedLanguage | 'docker' | 'bunnative'][] const scriptKindOptions: { value: Script['kind'] @@ -176,7 +184,7 @@ function initContent( language: SupportedLanguage, kind: Script['kind'] | undefined, - template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' + template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' ) { scriptEditor?.disableCollaboration() script.content = initialCode(language, kind, template) @@ -462,6 +470,16 @@ let deploymentMsg = '' let msgInput: HTMLInputElement | undefined = undefined + + function langToLanguage(lang: SupportedLanguage | 'docker' | 'bunnative'): SupportedLanguage { + if (lang == 'docker') { + return 'bash' + } + if (lang == 'bunnative') { + return 'bun' + } + return lang + } @@ -567,9 +585,12 @@ {/if}
+ {script.language} + {template} {#each langs as [label, lang] (lang)} {@const isPicked = (lang == script.language && template == 'script') || + (template == 'bunnative' && lang == 'bunnative') || (template == 'docker' && lang == 'docker')} = {} export let fixedOverflowWidgets = true diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte index a4b8bf06e5..f34a80d122 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/EmptyInlineScript.svelte @@ -7,7 +7,7 @@ import { inferArgs } from '$lib/infer' import { initialCode } from '$lib/script_helpers' import { emptySchema } from '$lib/utils' - import { defaultScriptLanguages, getScriptByPath } from '$lib/scripts' + import { defaultScriptLanguages, getScriptByPath, processLangs } from '$lib/scripts' import { Building, GitFork, Globe2 } from 'lucide-svelte' import { createEventDispatcher, getContext } from 'svelte' @@ -94,7 +94,7 @@ dispatch('new', unusedInlineScript.inlineScript) } - $: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages)) + $: langs = processLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages)) .map((l) => [defaultScriptLanguages[l], l]) .filter( (x) => diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte index eb924d4d6e..c0d6b545d0 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte @@ -314,10 +314,12 @@ if (inlineScript) { inlineScript.content = editor?.getCode() ?? '' } - runLoading = true - await Promise.all( - $runnableComponents[id]?.cb?.map((f) => f?.(inlineScript, true)) ?? [] - ) + try { + runLoading = true + await Promise.all( + $runnableComponents[id]?.cb?.map((f) => f?.(inlineScript, true)) ?? [] + ) + } catch {} runLoading = false }} on:change={async (e) => { diff --git a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte index 62dc74daac..1ef98ce443 100644 --- a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte +++ b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte @@ -26,17 +26,19 @@ | 'fetch' | 'docker' | 'powershell' + | 'bunnative' export let width = 30 export let height = 30 export let scale = 1 - const languageLabel: Record = { + const languageLabel: Record = { python3: 'Python', deno: 'TypeScript', go: 'Go', bash: 'Bash', powershell: 'PowerShell', nativets: 'HTTP', + bunnative: 'HTTP', graphql: 'GraphQL', postgresql: 'Postgresql', bigquery: 'BigQuery', @@ -48,7 +50,7 @@ } const langToComponent: Record< - SupportedLanguage | 'pgsql' | 'javascript' | 'fetch' | 'docker' | 'powershell', + SupportedLanguage | 'pgsql' | 'javascript' | 'fetch' | 'docker' | 'powershell' | 'bunnative', any > = { go: GoIcon, @@ -56,6 +58,7 @@ deno: TypeScriptIcon, // graphql: TypeScriptIcon, bun: TypeScriptIcon, + bunnative: RestIcon, bash: BashIcon, pgsql: PostgresIcon, mysql: MySQLIcon, diff --git a/frontend/src/lib/components/flows/content/FlowInputs.svelte b/frontend/src/lib/components/flows/content/FlowInputs.svelte index b3d8f87970..4880cdc161 100644 --- a/frontend/src/lib/components/flows/content/FlowInputs.svelte +++ b/frontend/src/lib/components/flows/content/FlowInputs.svelte @@ -14,7 +14,7 @@ import { Check, Code, Zap } from 'lucide-svelte' import SuspendDrawer from './SuspendDrawer.svelte' import { defaultScripts } from '$lib/stores' - import { defaultScriptLanguages } from '$lib/scripts' + import { defaultScriptLanguages, processLangs } from '$lib/scripts' import type { SupportedLanguage } from '$lib/common' import DefaultScripts from '$lib/components/DefaultScripts.svelte' @@ -34,7 +34,7 @@ let pick_existing: 'workspace' | 'hub' = 'hub' let filter = '' - $: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages)) + $: langs = processLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages)) .map((l) => [defaultScriptLanguages[l], l]) .filter( (x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]) @@ -161,7 +161,10 @@ {/if}

- Inline new {kind == 'script' ? 'action' : kind} script + Inline new {kind == 'script' ? 'action' : kind} + script

Use pre-made {kind == 'script' ? 'action' : kind} script

Use pre-made {kind == 'script' ? 'action' : kind} script

{#if pick_existing == 'hub'} diff --git a/frontend/src/lib/editorUtils.ts b/frontend/src/lib/editorUtils.ts index 632815014d..60756db075 100644 --- a/frontend/src/lib/editorUtils.ts +++ b/frontend/src/lib/editorUtils.ts @@ -42,6 +42,8 @@ export function langToExt(lang: string): string { switch (lang) { case 'javascript': return 'ts' + case 'bunnative': + return 'ts' case 'json': return 'json' case 'sql': diff --git a/frontend/src/lib/infer.ts b/frontend/src/lib/infer.ts index d23fa4ddbd..067f834b34 100644 --- a/frontend/src/lib/infer.ts +++ b/frontend/src/lib/infer.ts @@ -43,7 +43,7 @@ export function parseDeps(code: string): string[] { } export async function inferArgs( - language: SupportedLanguage | undefined, + language: SupportedLanguage | 'bunnative' | undefined, code: string, schema: Schema ): Promise { @@ -67,7 +67,7 @@ export async function inferArgs( inferedSchema = JSON.parse(parse_deno(code)) } else if (language == 'nativets') { inferedSchema = JSON.parse(parse_deno(code)) - } else if (language == 'bun') { + } else if (language == 'bun' || language == 'bunnative') { inferedSchema = JSON.parse(parse_deno(code)) } else if (language == 'postgresql') { inferedSchema = JSON.parse(parse_sql(code)) diff --git a/frontend/src/lib/script_helpers.ts b/frontend/src/lib/script_helpers.ts index a30a99913c..118fdc3566 100644 --- a/frontend/src/lib/script_helpers.ts +++ b/frontend/src/lib/script_helpers.ts @@ -25,6 +25,22 @@ export async function main(example_input: number = 3) { } ` +export const BUNNATIVE_INIT_CODE = `//native + +// native scripts are bun scripts that are executed on native workers and can be parallelized +// only fetch is allowed, but imports will work as long as they also use only fetch and the standard lib + +//import * as wmill from "windmill-client" + +export async function main(example_input: number = 3) { + // "3" is the default value of example_input, it can be overriden with code or using the UI + const res = await fetch(\`https://jsonplaceholder.typicode.com/todos/\${example_input}\`, { + headers: { "Content-Type": "application/json" }, + }); + return res.json(); +} +` + export const NATIVETS_INIT_CODE_CLEAR = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime //import * as wmill from './windmill.ts' @@ -459,9 +475,18 @@ export function isInitialCode(content: string): boolean { } export function initialCode( - language: SupportedLanguage | undefined, + language: SupportedLanguage | 'bunnative' | undefined, kind: Script['kind'] | undefined, - subkind: 'pgsql' | 'mysql' | 'flow' | 'script' | 'fetch' | 'docker' | 'powershell' | undefined + subkind: + | 'pgsql' + | 'mysql' + | 'flow' + | 'script' + | 'fetch' + | 'docker' + | 'powershell' + | 'bunnative' + | undefined ): string { if (!kind) { kind = 'script' @@ -524,8 +549,10 @@ export function initialCode( return GRAPHQL_INIT_CODE } else if (language == 'php') { return PHP_INIT_CODE - } else if (language == 'bun') { - if (kind === 'approval') { + } else if (language == 'bun' || language == 'bunnative') { + if (language == 'bunnative' || subkind === 'bunnative') { + return BUNNATIVE_INIT_CODE + } else if (kind === 'approval') { return BUN_INIT_CODE_APPROVAL } else if (kind === 'failure') { return BUN_FAILURE_MODULE_CODE @@ -549,7 +576,16 @@ export function initialCode( export function getResetCode( language: SupportedLanguage | undefined, kind: Script['kind'] | undefined, - subkind: 'pgsql' | 'mysql' | 'flow' | 'script' | 'fetch' | 'docker' | 'powershell' | undefined + subkind: + | 'pgsql' + | 'mysql' + | 'flow' + | 'script' + | 'fetch' + | 'docker' + | 'powershell' + | 'bunnative' + | undefined ) { if (language === 'deno') { return DENO_INIT_CODE_CLEAR diff --git a/frontend/src/lib/scripts.ts b/frontend/src/lib/scripts.ts index 0269064bad..fed474213a 100644 --- a/frontend/src/lib/scripts.ts +++ b/frontend/src/lib/scripts.ts @@ -4,10 +4,10 @@ import type { Schema, SupportedLanguage } from './common' import { FlowService, type Script, ScriptService, ScheduleService } from './gen' import { workspaceStore } from './stores' -export function scriptLangToEditorLang(lang: Script['language'] | undefined) { +export function scriptLangToEditorLang(lang: Script['language'] | 'bunnative' | undefined) { if (lang == 'deno') { return 'typescript' - } else if (lang == 'bun') { + } else if (lang == 'bun' || lang == 'bunnative') { return 'typescript' } else if (lang == 'nativets') { return 'typescript' @@ -92,13 +92,14 @@ export function scriptPathToHref(path: string, hubBaseUrl: string): string { } } -const scriptLanguagesArray: [SupportedLanguage | 'docker', string][] = [ +const scriptLanguagesArray: [SupportedLanguage | 'docker' | 'bunnative', string][] = [ ['bun', 'TypeScript (Bun)'], ['python3', 'Python'], ['deno', 'TypeScript (Deno)'], ['bash', 'Bash'], ['go', 'Go'], ['nativets', 'REST'], + ['bunnative', 'REST'], ['postgresql', 'PostgreSQL'], ['mysql', 'MySQL'], ['bigquery', 'BigQuery'], @@ -109,6 +110,18 @@ const scriptLanguagesArray: [SupportedLanguage | 'docker', string][] = [ ['php', 'PHP'], ['docker', 'Docker'] ] +export function processLangs(selected: string | undefined, langs: string[]): string[] { + if (selected === 'nativets') { + return langs + } else { + let ls = langs.filter((lang) => lang !== 'nativets') + if (!ls.includes('bunnative')) { + ls.push('bunnative') + } + return ls + } +} + export const defaultScriptLanguages = Object.fromEntries(scriptLanguagesArray) export async function getScriptByPath(path: string): Promise<{