Files
windmill/frontend/src/lib/script_helpers.ts
T
68debab877 feat(triggers): add AMQP (RabbitMQ) trigger via lapin (#10230)
* feat(triggers): add AMQP (RabbitMQ) trigger using the lapin library

Fixes WIN-2214

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(triggers): defer AMQP cross-workspace deploy pending utils-internal publish

Revert the amqp_trigger additions to the shared windmill-utils-internal
TriggerDeployKind and the frontend cross-workspace deploy adapter: the
frontend installs the published npm package, which lacks the new kind
until a release is cut. AMQP create/edit/delete/list/sync/capture are
unaffected (they use local types); only cross-workspace deploy/merge of
AMQP triggers waits on the package bump. Also document the at-most-once
ack in the consumer loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): address AMQP review — at-least-once ack, workspace cascade, contracts

- ack AMQP deliveries only after successful dispatch; nack+requeue on failure
- add ON DELETE CASCADE workspace FK so amqp_trigger rows are cleaned on
  workspace deletion (and the listener stops)
- fix the /amqp_triggers/test OpenAPI body and add amqp_trigger to
  WorkspaceDiffRow.kind
- register AMQP in the generated workspace trigger tool (create_trigger)
- drop banned $bindable defaults on optional props in the config section
- add build_uri unit tests (encoding, ports, vhost)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): stop AMQP poison-message loop and reconnect on transient drops

Chaos testing against a live RabbitMQ broker showed the previous
nack(requeue) + immediate re-poll spun a tight redelivery loop (~1000
critical-error reports/sec) on a poison message, and any connection blip
permanently disabled the trigger (lapin has no built-in reconnect).

- on dispatch failure: nack+requeue then stop consuming; the listener
  framework re-lists the trigger after its ping goes stale (~15s), backing
  redelivery off to that cadence instead of a tight loop (verified: rate
  dropped from ~1000/s to ~1 per ~26s, message preserved)
- on connection/stream error: stop and let the framework reconnect instead
  of disabling; persistent failures are still disabled via get_consumer
  (verified: a forced connection close now auto-reconnects and resumes)
- finish the AI create-trigger action wiring for AMQP: add amqp to
  CreatedResourceTriggerKind, the action-card registry, and the drawer
  registry so the result card renders and its "Open" action works

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): complete AMQP frontend registries and defer merge rows

- add amqp to capturableTriggerTypes (so AmqpCapture mounts), the Runs
  jobTriggerKinds filter, and CLOUD_DISABLED_TRIGGER_TYPES
- wire AMQP into global AI chat mode: TRIGGER_KINDS, the request union,
  writeTriggerSchema, triggerServices, and the draft adapter
- stop emitting actionable AMQP fork-comparison rows (revert amqp_trigger
  from TRIGGER_OR_SCHEDULE_TABLES) since cross-workspace deploy is deferred
  until windmill-utils-internal is published — avoids a deploy that fails
  with "Unknown kind: amqp_trigger"
- use design-system TextInput instead of raw <input> in the config section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): complete AMQP session/draft registries and constrain prefetch

- add amqp to the session-deploy, draft-compare, preview-router, and
  copilot workspace-item registries so AMQP drafts/deploys/nav/path
  resolution work
- include amqp_count in the MoveDrawer attached-trigger rename warning
- replace the raw prefetch <input> with a design-system TextInput bounded
  to an integer 1-65535 (backend u16) and block save on invalid values

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): make AMQP disconnect/reconnect consistent with the Kafka trigger

lapin, like rdkafka, has no transparent reconnect, so the AMQP listener now
mirrors the Kafka trigger's explicit reconnect loop instead of relying on the
framework re-list (which disabled the trigger once get_consumer failed on a
sustained outage):

- get_consumer returns cheaply; consume owns a (re)connect loop that retries
  with a 30s backoff, reports a critical error every 10 failed attempts, and
  reports a recovered critical error once it reconnects — never disabling the
  trigger on a connectivity failure
- a consumer/stream error breaks out to reconnect rather than disabling
- dispatch failure still nacks+requeues (at-least-once) with a short backoff
  to avoid a tight poison-message loop, keeping the connection alive

Verified against a live RabbitMQ broker: killing the broker keeps the trigger
enabled and retrying (attempt N), and restarting it auto-reconnects (logs
"reconnected after N attempts") and resumes dispatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): complete AMQP capture registries and constrain prefetch contract

- add the 'amqp' case to triggerKindToTriggerType so opening the AMQP editor
  from a capture button no longer throws "Unknown TriggerKind: amqp"
- register AmqpIcon in CaptureTable's icon map and add an AMQP entry to the
  script/flow CaptureButton menu
- bound the OpenAPI prefetch_count to an integer 1-65535 (matches the Rust
  u16) and regenerate clients/prompts
- require a non-empty exchange name when the exchange binding is enabled
- build_uri: fall back to "/" on a blank vhost and bracket IPv6 hosts (+ tests)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(triggers): wire AMQP into pipeline graph, git-sync, and preprocessor types

- asset_graph: discover attached amqp_trigger rows and emit an AMQP TriggerEdge
  so AMQP triggers render (and can be opened/deleted) on the data-pipeline canvas
- frontend pipeline graph: add amqp to NativeTriggerKind, the add-trigger menu,
  node presentation, event-trigger set, annotation keywords, and the
  editor/service registrations
- git-sync: add the amqp_trigger include pattern (+ test) so an AMQP git-sync
  deployment stages only its .amqp_trigger.* file, not an unrelated same-path object
- preprocessor starters: add the AMQP event to the generated TS/Python/PHP
  trigger event types (kind/payload/exchange/routing_key/queue_name/redelivered/
  delivery_tag)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): finish AMQP pipeline/parser wiring, prefetch validation, source lists

- fix a stray edit that corrupted the pre-existing MqttTriggerEditor import
  ($lib/... path) in PipelineTriggerEditors.svelte
- reject prefetch_count = 0 server-side in validate_config (RabbitMQ treats 0
  as unlimited) and defensively skip basic_qos(0) in build_consumer (covers
  the capture path that bypasses CRUD validation)
- recognize `// on amqp` in the canonical parser (TriggerSpec::Amqp) and add
  amqp to the CLI non-autorun/event-trigger sets so a pipeline cascade never
  runs an AMQP-only node as a manual root without an event
- add amqp to the preprocessor intro lists and both pipeline AI instructions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(triggers): reject zero AMQP prefetch in all paths and finish guidance lists

- extract a shared validate_amqp_options used by both CRUD validate_config
  and build_consumer, so capture configs (which bypass CRUD validation) also
  reject prefetch 0 instead of silently connecting with an unlimited buffer
  (+ unit tests for 0/1/65535/None)
- add AMQP to the main script-writing preprocessor-sources prompt and the CLI
  triggers-skill guidance list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(triggers): de-duplicate AMQP prefetch comment and fix GET response text

- keep the zero-prefetch rationale only on the shared validate_amqp_options
  doc; drop the redundant call-site comments
- correct the getAmqpTrigger OpenAPI 200 description ("deleted" -> "retrieved")

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to aaa6cb89b05b76139252c64f057e53b94d12ac60

This commit updates the EE repository reference after PR #680 was merged in windmill-ee-private.

Previous ee-repo-ref: 5da5fd65aca9594b2611837a52e4677b544b0380

New ee-repo-ref: aaa6cb89b05b76139252c64f057e53b94d12ac60

Automated by sync-ee-ref workflow.

* chore(migrations): consolidate the four AMQP migrations into one

The table and the three enum ADD VALUE statements (trigger_kind, job_trigger_kind,
draft_kind) are one atomic feature. ALTER TYPE ... ADD VALUE runs inside the
migration transaction on PG >= 14 (Windmill's minimum) since the amqp_trigger
table doesn't reference those enum types, so they can share a single migration
instead of four. Verified applying cleanly in a single transaction on a fresh DB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-07-21 15:10:29 +00:00

1790 lines
52 KiB
TypeScript

import { type Script } from './gen'
import type { SupportedLanguage } from './common'
import CLAUDE_SANDBOX_INIT_CODE from './templates/claude_sandbox.ts.template?raw'
import WAC_PYTHON_INIT_CODE from './templates/wac_python.py.template?raw'
import WAC_TYPESCRIPT_INIT_CODE from './templates/wac_typescript.ts.template?raw'
import CI_TEST_BUN_INIT_CODE from './templates/ci_test_bun.ts.template?raw'
import CI_TEST_PYTHON_INIT_CODE from './templates/ci_test_python.py.template?raw'
const PYTHON_FAILURE_MODULE_CODE = `import os
def main(message: str, name: str, step_id: str):
flow_id = os.environ.get("WM_ROOT_FLOW_JOB_ID")
print("message", message)
print("name", name)
print("step_id", step_id)
return { "message": message, "flow_id": flow_id, "step_id": step_id, "recover": False }`
const PYTHON_INIT_CODE_CLEAR = `# import wmill
def main(x: str):
return x`
const PYTHON_INIT_CODE_TRIGGER = `import wmill
def main():
# A common trigger script would follow this pattern:
# 1. Get the last saved state
# state = wmill.get_state()
# 2. Get the actual state from the external service
# newState = ...
# 3. Compare the two states and update the internal state
# wmill.setState(newState)
# 4. Return the new rows
# return range from (state to newState)
#
# For more complex states, consider using Data Tables:
# https://www.windmill.dev/docs/core_concepts/persistent_storage/data_tables
return [1, 2, 3]`
const PYTHON_INIT_CODE = `import os
import wmill
# You can import any PyPi package.
# See here for more info: https://www.windmill.dev/docs/advanced/dependencies_in_python
# you can use typed resources by doing a type alias to dict
#postgresql = dict
def main(
no_default: str,
#db: postgresql,
name="Nicolas Bourbaki",
age=42,
obj: dict = {"even": "dicts"},
l: list = ["or", "lists!"],
file_: bytes = bytes(0),
):
print(f"Hello World and a warm welcome especially to {name}")
print("and its acolytes..", age, obj, l, len(file_))
# retrieve variables, resources, states using the wmill client
try:
secret = wmill.get_variable("f/examples/secret")
except:
secret = "No secret yet at f/examples/secret !"
print(f"The variable at \`f/examples/secret\`: {secret}")
# Get last state of this script execution by the same trigger/user
last_state = wmill.get_state()
new_state = {"foo": 42} if last_state is None else last_state
new_state["foo"] += 1
wmill.set_state(new_state)
# fetch context variables
user = os.environ.get("WM_USERNAME")
# return value is converted to JSON
return {"splitted": name.split(), "user": user, "state": new_state}`
const NATIVETS_INIT_CODE = `// Fetch-only script, no imports allowed (except windmill) but benefits from a dedicated highly efficient runtime
//import * as wmill from './windmill.ts'
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();
}
`
const BUNNATIVE_INIT_CODE = `//native
//you can add proxy support using //proxy http(s)://host:port
// 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();
}
`
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'
export async function main() {
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1", {
headers: { "Content-Type": "application/json" },
});
return res.json();
}
`
const DENO_INIT_BLOCK = `// Ctrl/CMD+. to cache dependencies on imports hover.
// Deno uses "npm:" prefix to import from npm (https://deno.land/manual@v1.36.3/node/npm_specifiers)
// import * as wmill from "npm:windmill-client@${__pkg__.version}"
// fill the type, or use the +Resource type to get a type-safe reference to a resource
// type Postgresql = object`
const DENO_INIT_CODE =
DENO_INIT_BLOCK +
`
export async function main(
a: number,
b: "my" | "enum",
//c: Postgresql,
d = "inferred type string from default arg",
e = { nested: "object" },
//e: wmill.Base64
) {
// let x = await wmill.getVariable('u/user/foo')
return { foo: a };
}
`
const BUN_INIT_BLOCK = `// there are multiple modes to add as header: //nobundling //native //npm //nodejs
// https://www.windmill.dev/docs/getting_started/scripts_quickstart/typescript#modes
// import { toWords } from "number-to-words@1"
import * as wmill from "windmill-client"
// fill the type, or use the +Resource type to get a type-safe reference to a resource
// type Postgresql = object`
const BUN_INIT_CODE =
BUN_INIT_BLOCK +
`
export async function main(
a: number,
b: "my" | "enum",
//c: Postgresql,
//d: wmill.S3Object, // https://www.windmill.dev/docs/core_concepts/persistent_storage/large_data_files
//d: DynSelect_foo, // https://www.windmill.dev/docs/core_concepts/json_schema_and_parsing#dynamic-select
e = "inferred type string from default arg",
f = { nested: "object" },
g: {
label: "Variant 1",
foo: string
} | {
label: "Variant 2",
bar: number
}
) {
// let x = await wmill.getVariable('u/user/foo')
return { foo: a };
}
`
const GO_INIT_CODE = `package inner
import (
"fmt"
"rsc.io/quote"
// wmill "github.com/windmill-labs/windmill-go-client"
)
// Pin dependencies partially in go.mod with a comment starting with "//require":
//require rsc.io/quote v1.5.1
// the main must return (interface{}, error)
func main(x string, nested struct {
Foo string \`json:"foo"\`
}) (interface{}, error) {
fmt.Println("Hello, World")
fmt.Println(nested.Foo)
fmt.Println(quote.Opt())
// v, _ := wmill.GetVariable("f/examples/secret")
return x, nil
}
`
const GO_FAILURE_MODULE_CODE = `package inner
import (
"fmt"
"os"
)
// connect the error parameter to 'previous_result.error'
func main(message string, name string) (interface{}, error) {
fmt.Println(message)
fmt.Println(name)
fmt.Println("flow id that failed", os.Getenv("WM_FLOW_JOB_ID"))
return message, nil
}
`
const DENO_INIT_CODE_CLEAR = `// import * as wmill from "npm:windmill-client@${__pkg__.version}"
export async function main(x: string) {
return x
}
`
const BUN_INIT_CODE_CLEAR = `// import * as wmill from "windmill-client"
export async function main(x: string) {
return x
}
`
const DENO_FAILURE_MODULE_CODE = `
export async function main(message: string, name: string, step_id: string) {
const flow_id = Deno.env.get("WM_ROOT_FLOW_JOB_ID")
console.log("message", message)
console.log("name",name)
console.log("step_id", step_id)
return { message, flow_id, step_id, recover: false }
}
`
const BUN_FAILURE_MODULE_CODE = `
export async function main(message: string, name: string, step_id: string) {
const flow_id = process.env.WM_ROOT_FLOW_JOB_ID
console.log("message", message)
console.log("name",name)
console.log("step_id", step_id)
return { message, flow_id, step_id, recover: false }
}
`
const POSTGRES_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- to feed an S3Object (json/jsonl/parquet/csv) as a parameter, declare it as (s3object):
-- -- $5 input_file (s3object)
-- INSERT INTO demo SELECT * FROM jsonb_to_recordset(\$5::jsonb) AS x(id INT, name TEXT);
-- $1 name1 = default arg
-- $2 name2
-- $3 name3
-- $4 name4
INSERT INTO demo VALUES (\$1::TEXT, \$2::INT, \$3::TEXT[]) RETURNING *;
UPDATE demo SET col2 = \$4::INT WHERE col2 = \$2::INT;
`
const MYSQL_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- to feed an S3Object (json/jsonl/parquet/csv) as a parameter, declare it as (s3object):
-- -- :input_file (s3object)
-- INSERT INTO demo SELECT * FROM JSON_TABLE(:input_file, '$[*]' COLUMNS (id INT PATH '$.id', name VARCHAR(255) PATH '$.name')) AS x;
-- :name1 (text) = default arg
-- :name2 (int)
-- :name3 (int)
INSERT INTO demo VALUES (:name1, :name2);
UPDATE demo SET col2 = :name3 WHERE col2 = :name2;
`
const BIGQUERY_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- to feed an S3Object (json/jsonl/parquet/csv) as a parameter, declare it as (s3object):
-- -- @input_file (s3object)
-- SELECT * FROM UNNEST(JSON_QUERY_ARRAY(@input_file)) AS row;
-- @name1 (string) = default arg
-- @name2 (integer)
-- @name3 (string[])
-- @name4 (integer)
INSERT INTO \`demodb.demo\` VALUES (@name1, @name2, @name3);
UPDATE \`demodb.demo\` SET col2 = @name4 WHERE col2 = @name2;
`
const ORACLEDB_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- :name1 (text) = default arg
-- :name2 (int)
-- :name3 (int)
INSERT INTO demo VALUES (:name1, :name2);
UPDATE demo SET col2 = :name3 WHERE col2 = :name2;
`
const SNOWFLAKE_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- to feed an S3Object (json/jsonl/parquet/csv) as a parameter, declare it as (s3object):
-- -- ? input_file (s3object)
-- SELECT v.value:id::int AS id, v.value:name::string AS name
-- FROM TABLE(FLATTEN(input => PARSE_JSON(?))) v;
-- ? name1 (varchar) = default arg
-- ? name2 (int)
INSERT INTO demo VALUES (?, ?);
-- ? name3 (int)
-- ? name2 (int)
UPDATE demo SET col2 = ? WHERE col2 = ?;
`
const MSSQL_INIT_CODE = `-- result_collection=last_statement_all_rows
-- to pin the database use '-- database f/your/path'
-- to stream a large query result to your workspace storage use '-- s3'
-- to feed an S3Object (json/jsonl/parquet/csv) as a parameter, declare it as (s3object):
-- -- @P4 input_file (s3object)
-- INSERT INTO demo
-- SELECT id, name FROM OPENJSON(@P4) WITH (id INT '$.id', name NVARCHAR(255) '$.name');
-- @P1 name1 (varchar) = default arg
-- @P2 name2 (int)
-- @P3 name3 (int)
INSERT INTO demo VALUES (@P1, @P2);
UPDATE demo SET col2 = @P3 WHERE col2 = @P2;
`
const DUCKDB_INIT_CODE = `-- result_collection=last_statement_all_rows
-- $name (text) = Ben
-- $age (text) = 20
-- -- $friends_csv (s3object)
-- Click the +Database button to connect to a database
-- https://www.windmill.dev/docs/getting_started/scripts_quickstart/sql#duckdb-1
--
-- ATTACH '$res:u/demo/amazed_postgresql' AS db (TYPE postgres);
-- SELECT * FROM db.public.friends;
-- Click the +Ducklake button to use a ducklake
-- https://www.windmill.dev/docs/core_concepts/persistent_storage/ducklake
--
-- ATTACH 'ducklake' AS dl;
-- USE dl;
-- SELECT * FROM customers;
CREATE TABLE friends (
name text,
age int
);
INSERT INTO friends VALUES ($name, $age);
-- INSERT INTO friends
-- SELECT name, age FROM read_csv($friends_csv);
SELECT * FROM friends;
`
const GRAPHQL_INIT_CODE = `query($name4: String, $name2: Int, $name3: [String]) {
demo(name1: $name1, name2: $name2, name3: $name3) {
name1,
name2,
name3
}
}
`
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;
}
`
const RUST_INIT_CODE = `//! Add dependencies in the following partial Cargo.toml manifest
//!
//! \`\`\`cargo
//! [dependencies]
//! anyhow = "1.0.86"
//! rand = "0.7.2"
//! \`\`\`
//!
//! Note that serde is used by default with the \`derive\` feature.
//! You can still reimport it if you need additional features.
use anyhow::anyhow;
use rand::seq::SliceRandom;
use serde::Serialize;
#[derive(Serialize, Debug)]
struct Ret {
msg: String,
number: i8,
}
fn main(who_to_greet: String, numbers: Vec<i8>) -> anyhow::Result<Ret> {
println!(
"Person to greet: {} - numbers to choose: {:?}",
who_to_greet, numbers
);
Ok(Ret {
msg: format!("Greetings {}!", who_to_greet),
number: *numbers
.choose(&mut rand::thread_rng())
.ok_or(anyhow!("There should be some numbers to choose from"))?,
})
}
`
const CSHARP_INIT_CODE = `#r "nuget: Humanizer, 2.14.1"
using System;
using System.Linq;
using Humanizer;
class Script
{
public static int Main(string[] extraWords, string word = "clue", int highNumberThreshold = 50)
{
Console.WriteLine("Hello, World!");
Console.WriteLine("Your chosen words are pluralized here:");
string[] newWordArray = extraWords.Concat(new[] { word }).ToArray();
foreach (var s in newWordArray)
{
Console.WriteLine($" {s.Pluralize()}");
}
var random = new Random();
int randomNumber = random.Next(1, 101);
Console.WriteLine($"Random number: {randomNumber}");
string greeting = randomNumber > highNumberThreshold ? "High number!" : "Low number!";
greeting += " (according to the threshold parameter)";
Console.WriteLine(greeting);
// Humanize a timespan
var timespan = TimeSpan.FromMinutes(90);
Console.WriteLine($"Timespan: {timespan.Humanize()}");
// Humanize numbers into words
int number = 123;
Console.WriteLine($"Number: {number.ToWords()}");
// Pluralize words
string singular = "apple";
// Humanize date difference
var date = DateTime.UtcNow.AddDays(-3);
Console.WriteLine($"Date: {date.Humanize()}");
return 2;
}
}
`
const NU_INIT_CODE = `use std assert
# Nushell
# A new type of shell
def main [
no_default: string,
name = "Nicolas Bourbaki",
age: int = 42,
date_of_birth?: datetime,
obj: record = {"records": "included"},
l: list<string> = ["or", "lists!"],
tables?: table,
enable_kill_mode?: bool = true,
] {
# Test
# https://www.nushell.sh/book/testing.html
assert ($age == 42)
print $"Hello World and a warm welcome especially to ($name)"
print "and its acolytes.." $age $obj $l
print $tables
let secret = try {
get_variable f/examples/secret
} catch {
'No secret yet at f/examples/secret !'
};
print $"The variable at \`f/examples/secret\`: ($secret)"
# fetch context variables
let user = $env.WM_USERNAME
# Nu pipelines
ls | where size > 1kb | sort-by modified | print "ls:" $in
# Nu works with existing data
# Nu speaks JSON, YAML, SQLite, Excel, and more out of the box.
# It's easy to bring data into a Nu pipeline whether it's in a file, a database, or a web API:
let nu_license = http get https://api.github.com/repos/nushell/nushell | get license
return { splitted: ($name | split words), user: $user, nu_license: $nu_license}
# Interested in learning more?
# https://www.nushell.sh/book/getting_started.html
}
`
const FETCH_INIT_CODE = `export async function main(
url: string | undefined,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
body: Object = {},
headers: Record<string, string> = {}
): Promise<Response | null> {
if (!url) {
console.error('Error: URL is undefined')
return null
}
const requestOptions: RequestInit = {
method: method || 'GET',
headers: headers || {}
}
if (requestOptions.method !== 'GET' && requestOptions.method !== 'HEAD' && body !== undefined) {
requestOptions.body = JSON.stringify(body)
requestOptions.headers = {
'Content-Type': 'application/json',
...requestOptions.headers
}
}
return await fetch(url, requestOptions)
.then((res) => res.json())
.catch(() => {
throw new Error('An error occurred')
})
}`
const BASH_INIT_CODE = `# shellcheck shell=bash
# arguments of the form X="$I" are parsed as parameters X of type string
msg="$1"
dflt="\${2:-default value}"
# the last line of the stdout is the return value
# unless you write json to './result.json' or a string to './result.out'
echo "Hello $msg"
`
const DENO_INIT_CODE_TRIGGER = `import * as wmill from "npm:windmill-client@${__pkg__.version}"
export async function main() {
// A common trigger script would follow this pattern:
// 1. Get the last saved state
// const state = await wmill.getState()
// 2. Get the actual state from the external service
// const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json()
// 3. Compare the two states and update the internal state
// await wmill.setState(newState)
// 4. Return the new rows
// return range from (state to newState)
//
// For more complex states, consider using Data Tables:
// https://www.windmill.dev/docs/core_concepts/persistent_storage/data_tables
return [1,2,3]
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
// 'flow_input.iter.value'
}
`
const BUN_INIT_CODE_TRIGGER = `import * as wmill from "windmill-client"
export async function main() {
// A common trigger script would follow this pattern:
// 1. Get the last saved state
// const state = await wmill.getState()
// 2. Get the actual state from the external service
// const newState = await (await fetch('https://hacker-news.firebaseio.com/v0/topstories.json')).json()
// 3. Compare the two states and update the internal state
// await wmill.setState(newState)
// 4. Return the new rows
// return range from (state to newState)
//
// For more complex states, consider using Data Tables:
// https://www.windmill.dev/docs/core_concepts/persistent_storage/data_tables
return [1,2,3]
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
// 'flow_input.iter.value'
}
`
const GO_INIT_CODE_TRIGGER = `package inner
import (
wmill "github.com/windmill-labs/windmill-go-client"
)
func main() (interface{}, error) {
// A common trigger script would follow this pattern:
// 1. Get the last saved state
state, _ := wmill.GetState()
// 2. Get the actual state from the external service
// newState := ...
// 3. Compare the two states and update the internal state
wmill.SetState(4)
// 4. Return the new rows
//
// For more complex states, consider using Data Tables:
// https://www.windmill.dev/docs/core_concepts/persistent_storage/data_tables
return state, nil
// In subsequent scripts, you may refer to each row/value returned by the trigger script using
// 'flow_input.iter.value'
}
`
const DENO_INIT_CODE_APPROVAL = `import * as wmill from "npm:windmill-client@^1.158.2"
export async function main(approver?: string) {
const urls = await wmill.getResumeUrls(approver)
// send the urls to their intended recipients
return {
// if the resumeUrls are part of the response, they will be available to any persons having access
// to the run page and allowed to be approved from there, even from non owners of the flow
// self-approval is disableable in the suspend options
...urls,
// to have prompts (self-approvable steps), clude instead the resume url in the returned payload of the step
// the UX will automatically adapt and show the prompt to the operator when running the flow. e.g:
// resume: urls['resume'],
default_args: {},
enums: {},
description: undefined
// supports all formats from rich display rendering such as simple strings,
// but also markdown, html, images, tables, maps, render_all, etc...
// https://www.windmill.dev/docs/core_concepts/rich_display_rendering
}
}
// add a form in Advanced - Suspend
// all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
const BUN_INIT_CODE_APPROVAL = `import * as wmill from "windmill-client@^1.158.2"
export async function main(approver?: string) {
const urls = await wmill.getResumeUrls(approver)
// send the urls to their intended recipients
return {
// if the resumeUrls are part of the response, they will be available to any persons having access
// to the run page and allowed to be approved from there, even from non owners of the flow
// self-approval is disableable in the suspend options
...urls,
// to have prompts (self-approvable steps), clude instead the resume url in the returned payload of the step
// the UX will automatically adapt and show the prompt to the operator when running the flow. e.g:
// resume: urls['resume'],
default_args: {},
enums: {},
description: undefined
// supports all formats from rich display rendering such as simple strings,
// but also markdown, html, images, tables, maps, render_all, etc...
// https://www.windmill.dev/docs/core_concepts/rich_display_rendering
}
}
// add a form in Advanced - Suspend
// all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
export const TS_PREPROCESSOR_SCRIPT_INTRO = `/**
* Trigger preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
* before passing it to \`main\`. This separates the trigger logic from the main logic and keeps the auto-generated runnable UI clean.
*
* The returned object defines the parameter values passed to \`main()\`.
* e.g., { b: 1, a: 2 } → Calls \`main(2, 1)\`, assuming \`main\` is defined as \`main(a: number, b: number)\`.
* Ensure that the parameter names in \`main\` match the keys in the returned object.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/\n`
export const TS_PREPROCESSOR_FLOW_INTRO = `/**
* Trigger preprocessor
*
* It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
* before passing it to the flow. This separates the trigger logic from the flow logic and keeps the auto-generated UI clean.
*
* The returned object determines the parameter values passed to the flow.
* e.g., \`{ b: 1, a: 2 }\` → Calls the flow with \`a = 2\` and \`b = 1\`, assuming the flow has two inputs called \`a\` and \`b\`.
* Ensure that the input names of the flow match the keys in the returned object.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/\n`
export const TS_PREPROCESSOR_MODULE_CODE = `export async function preprocessor(event: TriggerEvent) {
return {
// return the args to be passed to the runnable
};
}
type TriggerEvent =
| {
kind: "webhook";
body: any;
raw_string: string | null;
query: Record<string, string>;
headers: Record<string, string>;
}
| {
kind: "http";
trigger_path: string;
body: any;
raw_string: string | null;
route: string;
path: string;
method: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
}
| {
kind: "email";
trigger_path: string;
parsed_email: any;
raw_email: string;
email_extra_args?: Record<string, string>;
}
| { kind: "websocket"; trigger_path: string; msg: string; url: string }
| {
kind: "kafka";
trigger_path: string;
payload: string;
brokers: string[];
topic: string;
partition: number;
offset: number;
group_id: string;
}
| {
kind: "nats";
trigger_path: string;
payload: string;
servers: string[];
subject: string;
headers?: Record<string, string[]>;
status?: number;
description?: string;
length: number;
}
| {
kind: "sqs";
trigger_path: string;
msg: string;
queue_url: string;
message_id?: string;
receipt_handle?: string;
attributes: Record<string, string>;
message_attributes?: Record<
string,
{ string_value?: string; data_type: string }
>;
}
| {
kind: "mqtt";
trigger_path: string;
payload: string;
topic: string;
retain: boolean;
pkid: number;
qos: number;
v5?: {
payload_format_indicator?: number;
topic_alias?: number;
response_topic?: string;
correlation_data?: Array<number>;
user_properties?: Array<[string, string]>;
subscription_identifiers?: Array<number>;
content_type?: string;
};
}
| {
kind: "amqp";
trigger_path: string;
payload: string;
exchange: string;
routing_key: string;
queue_name: string;
redelivered: boolean;
delivery_tag: number;
}
| {
kind: "gcp";
trigger_path: string;
payload: string;
message_id: string;
subscription: string;
ordering_key?: string;
attributes?: Record<string, string>;
delivery_type: "push" | "pull";
headers?: Record<string, string>;
publish_time?: string;
ack_id?: string;
}
| {
kind: "postgres";
trigger_path: string;
transaction_type: "insert" | "update" | "delete";
schema_name: string;
table_name: string;
old_row?: Record<string, any>;
row: Record<string, any>;
};
`
const PYTHON_INIT_CODE_APPROVAL = `import wmill
def main():
urls = wmill.get_resume_urls()
# send the urls to their intended recipients
return {
# if the get_resume_urls are part of the response, they will be available to any persons having access
# to the run page and allowed to be approved from there, even from non owners of the flow
# self-approval is disableable in the suspend options
**urls,
# to have prompts (self-approvable steps), clude instead the resume url in the returned payload of the step
# the UX will automatically adapt and show the prompt to the operator when running the flow. e.g:
# "resume": urls["resume"],
"default_args": {},
"enums": {},
"description": None,
# supports all formats from rich display rendering such as simple strings,
# but also markdown, html, images, tables, maps, render_all, etc...
# https://www.windmill.dev/docs/core_concepts/rich_display_rendering
}
# add a form in Advanced - Suspend
# all on approval steps: https://www.windmill.dev/docs/flows/flow_approval`
export const PYTHON_PREPROCESSOR_SCRIPT_INTRO = `# Trigger preprocessor
#
# ⚠️ This function runs BEFORE the main function.
#
# It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
# before passing it to \`main\`. This separates the trigger logic from the main logic and keeps the auto-generated UI clean.
#
# The returned object defines the parameter values passed to \`main()\`.
# e.g., { b: 1, a: 2 } → Calls \`main(2, 1)\`, assuming \`main\` is defined as \`main(a: int, b: int)\`.
# Ensure that the parameter names in \`main\` match the keys in the returned object.
#
# Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors\n\n`
export const PYTHON_PREPROCESSOR_FLOW_INTRO = `# Trigger preprocessor
#
# It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
# before passing it to the flow. This separates the trigger logic from the flow logic and keeps the auto-generated UI clean.
#
# The returned object determines the parameter values passed to the flow.
# e.g., \`{ b: 1, a: 2 }\` → Calls the flow with \`a = 2\` and \`b = 1\`, assuming the flow has two inputs called \`a\` and \`b\`.
# Ensure that the input names of the flow match the keys in the returned object.
#
# Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors\n\n`
export const PYTHON_PREPROCESSOR_MODULE_CODE = `from typing import TypedDict, Literal, Optional, Union
class WebhookEvent(TypedDict):
kind: Literal["webhook"]
body: dict
raw_string: Optional[str]
query: dict[str, str]
headers: dict[str, str]
class HttpEvent(TypedDict):
kind: Literal["http"]
trigger_path: str
body: dict
raw_string: Optional[str]
route: str
path: str
method: str
params: dict[str, str]
query: dict[str, str]
headers: dict[str, str]
class EmailEvent(TypedDict):
kind: Literal["email"]
trigger_path: str
parsed_email: dict
raw_email: str
email_extra_args: Optional[dict[str, str]]
class WebsocketEvent(TypedDict):
kind: Literal["websocket"]
trigger_path: str
msg: str
url: str
class KafkaEvent(TypedDict):
kind: Literal["kafka"]
trigger_path: str
payload: str
brokers: list[str]
topic: str
partition: int
offset: int
group_id: str
class NatsEvent(TypedDict):
kind: Literal["nats"]
trigger_path: str
payload: str
servers: list[str]
subject: str
headers: Optional[dict[str, list[str]]]
status: Optional[int]
description: Optional[str]
length: int
class MessageAttribute(TypedDict):
string_value: Optional[str]
data_type: str
class SqsEvent(TypedDict):
kind: Literal["sqs"]
trigger_path: str
msg: str
queue_url: str
message_id: Optional[str]
receipt_handle: Optional[str]
attributes: dict[str, str]
message_attributes: Optional[dict[str, MessageAttribute]]
class MqttV5Properties(TypedDict, total=False):
payload_format_indicator: Optional[int]
topic_alias: Optional[int]
response_topic: Optional[str]
correlation_data: Optional[list[int]]
user_properties: Optional[list[tuple[str, str]]]
subscription_identifiers: Optional[list[int]]
content_type: Optional[str]
class MqttEvent(TypedDict):
kind: Literal["mqtt"]
trigger_path: str
payload: str
topic: str
retain: bool
pkid: int
qos: int
v5: Optional[MqttV5Properties]
class AmqpEvent(TypedDict):
kind: Literal["amqp"]
trigger_path: str
payload: str
exchange: str
routing_key: str
queue_name: str
redelivered: bool
delivery_tag: int
class GcpEvent(TypedDict):
kind: Literal["gcp"]
trigger_path: str
payload: str
message_id: str
subscription: str
ordering_key: Optional[str]
attributes: Optional[dict[str, str]]
delivery_type: Literal["push", "pull"]
headers: Optional[dict[str, str]]
publish_time: Optional[str]
ack_id: Optional[str]
class PostgresEvent(TypedDict):
kind: Literal["postgres"]
trigger_path: str
transaction_type: Literal["insert", "update", "delete"]
schema_name: str
table_name: str
old_row: Optional[dict[str, any]]
row: dict[str, any]
Event = Union[
WebhookEvent,
HttpEvent,
EmailEvent,
WebsocketEvent,
KafkaEvent,
NatsEvent,
SqsEvent,
MqttEvent,
AmqpEvent,
GcpEvent,
PostgresEvent,
]
def preprocessor(event: Event):
return {
# return the args to be passed to the runnable
}
`
export const PHP_PREPROCESSOR_SCRIPT_INTRO = `<?php
/**
* Trigger preprocessor
*
* ⚠️ This function runs BEFORE the main function.
*
* It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
* before passing it to \`main\`. This separates the trigger logic from the main logic and keeps the auto-generated runnable UI clean.
*
* The returned object defines the parameter values passed to \`main()\`.
* e.g., ['b' => 1, 'a' => 2] → Calls \`main(2, 1)\`, assuming \`main\` is defined as \`main($a, $b)\`.
* Ensure that the parameter names in \`main\` match the keys in the returned array.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
`
export const PHP_PREPROCESSOR_FLOW_INTRO = `<?php
/**
* Trigger preprocessor
*
* It processes raw trigger data from various sources (webhook, custom HTTP route, SQS, WebSocket, Kafka, NATS, MQTT, AMQP, Postgres, or email)
* before passing it to the flow. This separates the trigger logic from the flow logic and keeps the auto-generated UI clean.
*
* The returned object determines the parameter values passed to the flow.
* e.g., ['b' => 1, 'a' => 2] → Calls the flow with \`a = 2\` and \`b = 1\`, assuming the flow has two inputs called \`a\` and \`b\`.
* Ensure that the input names of the flow match the keys in the returned array.
*
* Learn more: https://www.windmill.dev/docs/core_concepts/preprocessors
*/
`
export const PHP_PREPROCESSOR_MODULE_CODE = `function preprocessor(object $event) {
// $event can be one of the following types:
//
// All events (except webhook) include 'trigger_path' => '...' (the path of the trigger in Windmill)
//
// Webhook event:
// ['kind' => 'webhook', 'body' => [...], 'raw_string' => '...', 'query' => [...], 'headers' => [...]]
//
// HTTP event:
// ['kind' => 'http', 'trigger_path' => '...', 'body' => [...], 'raw_string' => '...', 'route' => '...', 'path' => '...',
// 'method' => '...', 'params' => [...], 'query' => [...], 'headers' => [...]]
//
// Email event:
// ['kind' => 'email', 'trigger_path' => '...', 'parsed_email' => [...], 'raw_email' => '...', 'email_extra_args' => [...]]
//
// WebSocket event:
// ['kind' => 'websocket', 'trigger_path' => '...', 'msg' => '...', 'url' => '...']
//
// Kafka event:
// ['kind' => 'kafka', 'trigger_path' => '...', 'payload' => '...', 'brokers' => [...], 'topic' => '...',
// 'partition' => 0, 'offset' => 0, 'group_id' => '...']
//
// NATS event:
// ['kind' => 'nats', 'trigger_path' => '...', 'payload' => '...', 'servers' => [...], 'subject' => '...',
// 'headers' => [...], 'status' => 200, 'description' => '...', 'length' => 100]
//
// SQS event:
// ['kind' => 'sqs', 'trigger_path' => '...', 'msg' => '...', 'queue_url' => '...', 'message_id' => '...',
// 'receipt_handle' => '...', 'attributes' => [...], 'message_attributes' => [...]]
//
// MQTT event:
// ['kind' => 'mqtt', 'trigger_path' => '...', 'payload' => '...', 'topic' => '...', 'retain' => true, 'pkid' => 1,
// 'qos' => 1, 'v5' => [...]]
//
// AMQP event:
// ['kind' => 'amqp', 'trigger_path' => '...', 'payload' => '...', 'exchange' => '...', 'routing_key' => '...',
// 'queue_name' => '...', 'redelivered' => false, 'delivery_tag' => 1]
//
// GCP event:
// ['kind' => 'gcp', 'trigger_path' => '...', 'payload' => '...', 'message_id' => '...', 'subscription' => '...',
// 'ordering_key' => '...', 'attributes' => [...], 'delivery_type' => 'push',
// 'headers' => [...], 'publish_time' => '...', 'ack_id' => '...']
//
// Postgres event:
// ['kind' => 'postgres', 'trigger_path' => '...', 'transaction_type' => 'insert', 'schema_name' => '...',
// 'table_name' => '...', 'old_row' => [...], 'row' => [...]]
return [
// return the args to be passed to the runnable
];
}
`
const DOCKER_INIT_CODE = `# shellcheck shell=bash
# sandbox alpine:latest
# The "# sandbox <image>" annotation runs this script INSIDE the image above,
# sandboxed via nsjail: the image's rootfs is extracted (rootless podman) and the
# body runs chrooted in it, inheriting the job's confinement. The body runs with
# the image's /bin/sh and windmill args bind positionally as $1, $2, ...
# Daemonless — no docker run/-d/exec/build and no host -v bind mounts.
# (A bare "# docker" still uses the legacy daemon runtime instead.)
msg="\${1:-world}"
echo "Hello $msg"
cat /etc/os-release | head -1
`
const POWERSHELL_INIT_CODE = `param($Msg, [string[]]$Names, [PSCustomObject]$Obj, $Dflt = "default value", [int]$Nb = 3)
# Import-Module MyModule
# Import-Module WindmillClient
# Connect-Windmill
# Get-WindmillVariable -Path 'u/user/foo'
# the last line of the stdout is the return value
Write-Output "Hello $Msg"`
const ANSIBLE_PLAYBOOK_INIT_CODE = `---
inventory:
- resource_type: ansible_inventory
# You can pin an inventory to this script by hardcoding the resource path:
# resource: u/user/your_resource
# - name: hcloud.yml
# resource_type: dynamic_inventory
options:
- verbosity: vvv
# File resources will be written in the relative \`target\` location before
# running the playbook
# files:
# - resource: u/user/fabulous_jinja_template
# target: ./config_template.j2
# - variable: u/user/ssh_key
# target: ./ssh_key
# mode: '0600'
# Define the arguments of the windmill script
extra_vars:
world_qualifier:
type: string
# If using Ansible Vault:
# vault_password: u/user/ansible_vault_password
dependencies:
galaxy:
collections:
- name: community.general
- name: community.vmware
roles:
python:
- jmespath
---
- name: Echo
hosts: 127.0.0.1
connection: local
vars:
my_result:
a: 2
b: true
c: "Hello"
tasks:
- name: Print debug message
debug:
msg: "Hello, {{world_qualifier}} world!"
- name: Write variable my_result to result.json
delegate_to: localhost
copy:
content: "{{ my_result | to_json }}"
dest: result.json
`
const JAVA_INIT_CODE = `//requirements:
//com.google.code.gson:gson:2.8.9
//com.github.ricksbrown:cowsay:1.1.0
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.github.ricksbrown.cowsay.Cowsay;
import com.github.ricksbrown.cowsay.plugin.CowExecutor;
public class Main {
public static class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public static Object main(
// Primitive
int a,
float b,
// Objects
Integer age,
Float d,
Object e,
String name,
// Lists
String[] f
// No trailing commas!
){
Gson gson = new Gson();
// Get resources
var theme = Wmill.getResource("f/app_themes/theme_0");
System.out.println("Theme: " + theme);
// Create a Person object
Person person = new Person( (name == "") ? "Alice" : name, (age == null) ? 30 : age);
// Serialize the Person object to JSON
String json = gson.toJson(person);
System.out.println("Serialized JSON: " + json);
// Use cowsay
String[] args = new String[]{"-f", "dragon", json };
String result = Cowsay.say(args);
return result;
}
}
`
const RUBY_INIT_CODE = `require 'windmill/inline'
require 'windmill/mini'
# Dependency management: declare gems in gemfile block for automatic installation
# Windmill uses bundler/inline compatible syntax with automatic requiring
gemfile do
source 'https://rubygems.org'
gem 'amazing_print', '~> 1.6'
end
def main(
no_default,
name = "Nicolas Bourbaki",
age = 42,
obj = { "even": "hashes" },
list = ["or", "arrays!"]
)
puts "Hello World and a warm welcome especially to #{name}"
puts "and its acolytes.. #{age} #{obj} #{list}"
# Retrieve variables using the Windmill mini client
begin
secret = get_variable("f/examples/secret")
rescue => e
secret = "No secret yet at f/examples/secret!"
end
puts "The variable at 'f/examples/secret': #{secret}"
# Get typed resources using the mini client
# database = get_resource("u/user/my_postgresql")
# Access environment variables provided by Windmill
user = ENV['WM_USERNAME']
# Pretty print results using amazing_print (automatically required from gemfile)
result = {
"splitted" => name.split,
"user" => user,
"age" => age,
"obj" => obj,
"list" => list
}
ap result
# Return value is automatically converted to JSON
return result
end
`
const R_INIT_CODE = `library(dplyr)
library(jsonlite)
main <- function(
x,
name = "default",
age = 25,
data = list(1, 2, 3),
flag = TRUE
) {
# Use Windmill helpers:
# var <- get_variable("f/my_var")
# res <- get_resource("f/my_resource")
df <- tibble(name = name, age = age, x = x)
result <- df %>% mutate(greeting = paste("Hello", name))
return(toJSON(result, auto_unbox = TRUE))
}
`
// for related places search: ADD_NEW_LANG
export const INITIAL_CODE = {
bun: {
scriptInitCodeBlock: BUN_INIT_BLOCK,
script: BUN_INIT_CODE,
trigger: BUN_INIT_CODE_TRIGGER,
approval: BUN_INIT_CODE_APPROVAL,
failure: BUN_FAILURE_MODULE_CODE,
preprocessor: TS_PREPROCESSOR_FLOW_INTRO + TS_PREPROCESSOR_MODULE_CODE,
clear: BUN_INIT_CODE_CLEAR
},
python3: {
script: PYTHON_INIT_CODE,
trigger: PYTHON_INIT_CODE_TRIGGER,
approval: PYTHON_INIT_CODE_APPROVAL,
failure: PYTHON_FAILURE_MODULE_CODE,
preprocessor: PYTHON_PREPROCESSOR_FLOW_INTRO + PYTHON_PREPROCESSOR_MODULE_CODE,
clear: PYTHON_INIT_CODE_CLEAR
},
deno: {
scriptInitCodeBlock: DENO_INIT_BLOCK,
script: DENO_INIT_CODE,
trigger: DENO_INIT_CODE_TRIGGER,
approval: DENO_INIT_CODE_APPROVAL,
failure: DENO_FAILURE_MODULE_CODE,
preprocessor: TS_PREPROCESSOR_FLOW_INTRO + TS_PREPROCESSOR_MODULE_CODE,
fetch: FETCH_INIT_CODE,
clear: DENO_INIT_CODE_CLEAR
},
go: {
script: GO_INIT_CODE,
trigger: GO_INIT_CODE_TRIGGER,
failure: GO_FAILURE_MODULE_CODE
},
bash: {
script: BASH_INIT_CODE
},
powershell: {
script: POWERSHELL_INIT_CODE
},
nativets: {
script: NATIVETS_INIT_CODE
},
postgresql: {
script: POSTGRES_INIT_CODE
},
mysql: {
script: MYSQL_INIT_CODE
},
bigquery: {
script: BIGQUERY_INIT_CODE
},
snowflake: {
script: SNOWFLAKE_INIT_CODE
},
mssql: {
script: MSSQL_INIT_CODE
},
duckdb: {
script: DUCKDB_INIT_CODE
},
graphql: {
script: GRAPHQL_INIT_CODE
},
oracledb: {
script: ORACLEDB_INIT_CODE
},
php: {
script: PHP_INIT_CODE,
preprocessor: PHP_PREPROCESSOR_FLOW_INTRO + PHP_PREPROCESSOR_MODULE_CODE
},
rust: {
script: RUST_INIT_CODE
},
ansible: {
script: ANSIBLE_PLAYBOOK_INIT_CODE
},
csharp: {
script: CSHARP_INIT_CODE
},
nu: {
script: NU_INIT_CODE
},
docker: {
script: DOCKER_INIT_CODE
},
bunnative: {
script: BUNNATIVE_INIT_CODE
},
java: {
script: JAVA_INIT_CODE
},
ruby: {
script: RUBY_INIT_CODE
},
rlang: {
script: R_INIT_CODE
},
claudesandbox: {
script: CLAUDE_SANDBOX_INIT_CODE
},
wac_python: {
script: WAC_PYTHON_INIT_CODE
},
wac_typescript: {
script: WAC_TYPESCRIPT_INIT_CODE
},
ci_test_bun: {
script: CI_TEST_BUN_INIT_CODE
},
ci_test_python: {
script: CI_TEST_PYTHON_INIT_CODE
}
// for related places search: ADD_NEW_LANG
}
/**
* Whether a bash script body runs inside a custom container image that does not
* ship the `wmill` CLI (nor `jq`), namely `# sandbox <image>` or `# docker`. In
* that case editor snippets must fall back to a plain HTTP client instead of `wmill`.
*
* Mirrors the worker's annotation grammar (backend/windmill-common/src/worker.rs,
* `BashAnnotations`): only leading comment lines are scanned, stopping at the first
* non-comment line. A bare `# sandbox` (no image) is the nsjail-bash modifier that
* still runs on the worker rootfs where `wmill` is available, so it is excluded.
*/
export function bashRunsInCustomImage(code: string): boolean {
for (const line of code.split('\n')) {
const trimmed = line.trim()
if (trimmed === '') continue
if (!trimmed.startsWith('#')) break
const tokens = trimmed.slice(1).trim().split(/\s+/)
// `# sandbox <image>` selects a container; bare `# sandbox` does not.
if (tokens[0] === 'sandbox' && tokens[1]) return true
// `# docker` (v1 daemon runtime) runs in the referenced image, no wmill.
if (tokens[0] === 'docker' && tokens.length === 1) return true
}
return false
}
export function isInitialCode(content: string): boolean {
for (const lang of Object.values(INITIAL_CODE)) {
for (const code of Object.values(lang)) {
if (content === code) {
return true
}
}
}
return false
}
export function initialCode(
language: SupportedLanguage | 'bunnative' | undefined,
kind: Script['kind'] | undefined,
subkind:
| 'pgsql'
| 'mysql'
| 'flow'
| 'script'
| 'fetch'
| 'docker'
| 'powershell'
| 'bunnative'
| 'claudesandbox'
| 'wac_python'
| 'wac_typescript'
| 'ci_test_bun'
| 'ci_test_python'
| undefined,
templateScript?: boolean
): string {
if (!kind) {
kind = 'script'
}
if (language === 'deno') {
if (kind === 'trigger') {
return INITIAL_CODE.deno.trigger
} else if (kind === 'script') {
if (subkind === 'flow') {
return INITIAL_CODE.deno.clear
} else if (subkind === 'pgsql') {
return INITIAL_CODE.postgresql.script
} else if (subkind === 'mysql') {
return INITIAL_CODE.mysql.script
} else if (subkind === 'fetch') {
return INITIAL_CODE.deno.fetch
} else {
return INITIAL_CODE.deno.script
}
} else if (kind === 'failure') {
return INITIAL_CODE.deno.failure
} else if (kind === 'approval') {
return INITIAL_CODE.deno.approval
} else if (kind === 'preprocessor') {
return INITIAL_CODE.deno.preprocessor
} else {
return INITIAL_CODE.deno.script
}
} else if (subkind === 'ci_test_bun') {
return INITIAL_CODE.ci_test_bun.script
} else if (subkind === 'ci_test_python') {
return INITIAL_CODE.ci_test_python.script
} else if (subkind === 'wac_python') {
return INITIAL_CODE.wac_python.script
} else if (subkind === 'wac_typescript') {
return INITIAL_CODE.wac_typescript.script
} else if (language === 'python3') {
if (kind === 'trigger') {
return INITIAL_CODE.python3.trigger
} else if (kind === 'approval') {
return INITIAL_CODE.python3.approval
} else if (kind === 'failure') {
return INITIAL_CODE.python3.failure
} else if (kind === 'preprocessor') {
return INITIAL_CODE.python3.preprocessor
} else if (subkind === 'flow') {
return INITIAL_CODE.python3.clear
} else {
return INITIAL_CODE.python3.script
}
} else if (language == 'bash') {
if (subkind === 'docker') {
return INITIAL_CODE.docker.script
} else {
return INITIAL_CODE.bash.script
}
} else if (language == 'powershell') {
return INITIAL_CODE.powershell.script
} else if (language == 'nativets') {
return INITIAL_CODE.nativets.script
} else if (language == 'postgresql') {
return INITIAL_CODE.postgresql.script
} else if (language == 'mysql') {
return INITIAL_CODE.mysql.script
} else if (language == 'bigquery') {
return INITIAL_CODE.bigquery.script
} else if (language == 'oracledb') {
return INITIAL_CODE.oracledb.script
} else if (language == 'snowflake') {
return INITIAL_CODE.snowflake.script
} else if (language == 'mssql') {
return INITIAL_CODE.mssql.script
} else if (language == 'graphql') {
return INITIAL_CODE.graphql.script
} else if (language == 'duckdb') {
return INITIAL_CODE.duckdb.script
} else if (language == 'php') {
if (kind == 'preprocessor') {
return INITIAL_CODE.php.preprocessor
}
return INITIAL_CODE.php.script
} else if (language == 'rust') {
return INITIAL_CODE.rust.script
} else if (language == 'ansible') {
return INITIAL_CODE.ansible.script
} else if (language == 'csharp') {
return INITIAL_CODE.csharp.script
} else if (language == 'nu') {
return INITIAL_CODE.nu.script
} else if (language == 'java') {
return INITIAL_CODE.java.script
} else if (language == 'ruby') {
return INITIAL_CODE.ruby.script
} else if (language == 'rlang') {
return INITIAL_CODE.rlang.script
// for related places search: ADD_NEW_LANG
} else if (language == 'bun' || language == 'bunnative') {
if (subkind === 'claudesandbox') {
return INITIAL_CODE.claudesandbox.script
} else if (kind == 'trigger') {
return INITIAL_CODE.bun.trigger
} else if (language == 'bunnative' || subkind === 'bunnative') {
return INITIAL_CODE.bunnative.script
} else if (kind === 'approval') {
return INITIAL_CODE.bun.approval
} else if (kind === 'failure') {
return INITIAL_CODE.bun.failure
} else if (kind === 'preprocessor') {
return INITIAL_CODE.bun.preprocessor
} else if (templateScript == true) {
return INITIAL_CODE.bun.scriptInitCodeBlock
} else if (subkind === 'flow') {
return INITIAL_CODE.bun.clear
}
return INITIAL_CODE.bun.script
} else {
if (kind === 'failure') {
return INITIAL_CODE.go.failure
} else if (kind === 'trigger') {
return INITIAL_CODE.go.trigger
} else {
return INITIAL_CODE.go.script
}
}
}
export function getResetCode(
language: SupportedLanguage | 'bunnative' | undefined,
kind: Script['kind'] | undefined,
subkind:
| 'pgsql'
| 'mysql'
| 'flow'
| 'script'
| 'fetch'
| 'docker'
| 'powershell'
| 'bunnative'
| 'claudesandbox'
| 'wac_python'
| 'wac_typescript'
| 'ci_test_bun'
| 'ci_test_python'
| undefined
) {
if (language === 'deno') {
return DENO_INIT_CODE_CLEAR
} else if (language === 'python3') {
return PYTHON_INIT_CODE_CLEAR
} else if (language === 'nativets') {
return NATIVETS_INIT_CODE_CLEAR
} else if (language === 'bun') {
return BUN_INIT_CODE_CLEAR
} else if (language === 'bunnative') {
return BUNNATIVE_INIT_CODE
} else {
return initialCode(language, kind, subkind)
}
}
export const PREPROCESSOR_SUPPORTED_LANGUAGES = [
'typescript',
'python',
'python3',
'deno',
'bun',
'php'
] as const
export function canHavePreprocessor(language: string | undefined): boolean {
if (!language) {
return false
}
return PREPROCESSOR_SUPPORTED_LANGUAGES.includes(language as any)
}
export function canHaveTrigger(language: SupportedLanguage | undefined): boolean {
if (!language) {
return false
}
return ['python3', 'bun', 'deno', 'go'].includes(language)
}
export function canHaveApproval(language: SupportedLanguage | undefined): boolean {
if (!language) {
return false
}
return ['python3', 'bun'].includes(language)
}
export function canHaveFailure(language: SupportedLanguage | undefined): boolean {
if (!language) {
return false
}
return ['python3', 'bun', 'deno', 'go'].includes(language)
}
export function getPreprocessorIntro(
language: SupportedLanguage | 'docker' | 'bunnative' | undefined,
isFlow: boolean = false
): string {
if (!language || !PREPROCESSOR_SUPPORTED_LANGUAGES.includes(language as any)) {
return ''
}
switch (language) {
case 'python3':
return isFlow ? PYTHON_PREPROCESSOR_FLOW_INTRO : PYTHON_PREPROCESSOR_SCRIPT_INTRO
case 'deno':
case 'bun':
return isFlow ? TS_PREPROCESSOR_FLOW_INTRO : TS_PREPROCESSOR_SCRIPT_INTRO
case 'php':
return isFlow ? PHP_PREPROCESSOR_FLOW_INTRO : PHP_PREPROCESSOR_SCRIPT_INTRO
default:
return ''
}
}
export function getPreprocessorModuleCode(
language: SupportedLanguage | 'docker' | 'bunnative' | undefined
): string {
if (!language || !PREPROCESSOR_SUPPORTED_LANGUAGES.includes(language as any)) {
return ''
}
switch (language) {
case 'python3':
return PYTHON_PREPROCESSOR_MODULE_CODE
case 'deno':
case 'bun':
return TS_PREPROCESSOR_MODULE_CODE
case 'php':
return PHP_PREPROCESSOR_MODULE_CODE
default:
return ''
}
}
export function getPreprocessorFullCode(
language: SupportedLanguage | 'docker' | 'bunnative' | undefined,
isFlow: boolean = false
): string {
const intro = getPreprocessorIntro(language, isFlow)
const moduleCode = getPreprocessorModuleCode(language)
return intro + moduleCode
}
export function getMainFunctionPattern(
language: SupportedLanguage | 'docker' | 'bunnative' | undefined
): string {
if (!language) {
return ''
}
switch (language) {
case 'python3':
return 'def main'
case 'deno':
case 'bun':
case 'nativets':
return 'export async function main'
case 'php':
return 'function main'
default:
return 'main'
}
}