Files
windmill/frontend/src/lib/script_helpers.ts
T
Ruben Fiszel d578e40101 feat: add selfApproval option to WAC + inline approval buttons (#8440)
* feat: add selfApproval option to WAC waitForApproval + inline approval buttons

Add self-approval configuration to WAC workflows and inline
approve/reject buttons in WorkflowTimeline.

- TS SDK: add selfApproval option to waitForApproval()
- Python SDK: add self_approval param to wait_for_approval()
- Backend: store approval_conditions in flow_status for WAC,
  enforce self-approval checks on resume endpoints
- Frontend: show Approve/Reject buttons in timeline with form
  support (EE), gated by user permissions

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

* fix: revert sqlx query change + regenerate system prompts

- Revert get_suspended_flow_info to use original sqlx::query_as!
  with COALESCE to avoid sqlx offline cache mismatch in CI
- Detect WAC by checking if FlowStatus parsing fails + suspend > 0
- Re-fetch flow_status column separately for WAC approval conditions
- Regenerate auto-generated system prompt files for SDK changes

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

* feat: use resume URLs for WAC inline approval buttons

- Backend generates HMAC-signed resume/cancel URLs when creating
  WAC approval, stores them in timeline entry and approval meta
- Frontend uses anonymous resume endpoint (like classic flows)
  with fallback to resumeSuspendedFlowAsOwner for admins
- Buttons show for everyone when URLs are present; server-side
  self_approval_disabled check enforces restrictions
- Show warning for admins/owners when self-approval is disabled
- selfApproval: false requires EE (errors at dispatch on CE)
- self_approval_disabled check moved outside user_auth_required
  gate so it works independently
- WAC detection no longer requires task import

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

* feat: add resume_suspended and approval_info endpoints

- New approval_token DB table for token-based approval access
- New POST /jobs_u/flow/resume_suspended/{job_id} endpoint:
  - OptAuthed: works with login or approval_token
  - Checks approval_conditions (self_approval, groups, auth)
  - Admins/owners bypass rules
- New GET /jobs_u/flow/approval_info/{job_id} endpoint:
  - Returns form, rules, can_approve status
- HMAC anonymous endpoint now bypasses all approval_conditions
  (secret = full capability)
- getResumeUrls approvalPage URL now uses token format
- WAC approval dispatch generates and stores approval tokens
- Mark resumeSuspendedFlowAsOwner as legacy

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

* feat: simplify frontend to use resume_suspended endpoint

- OpenAPI spec updated with resume_suspended and approval_info endpoints
- WorkflowTimeline: removed URL parsing, now calls single
  resumeSuspended endpoint for both approve and reject
- Buttons show for any logged-in user viewing the job (backend
  enforces authorization rules)
- Kept self-approval warning for admins

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

* feat: stateless approval tokens, new approval page, FlowStatusWaitingForEvents update

- Replace DB-stored approval tokens with stateless HMAC derivation:
  token = HMAC(workspace_key, job_id + "approval_token")
  Verifiable without DB lookup, not reversible to resume secret
- Drop approval_token migration (no DB table needed)
- FlowStatusWaitingForEvents: use resumeSuspended endpoint instead
  of URL parsing + resumeSuspendedFlowAsOwner
- New approval page route /approve/{ws}/{job}?token= that uses
  approval_info and resume_suspended endpoints
- Old approval page route kept for back-compat

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

* feat: match old approval page content in new approval page

- Add FlowMetadata, JobArgs, FlowGraphV2, DisplayResult
- Add approvers with tooltips, flow arguments section
- Add admin self-approval bypass warning
- Add "Open run details" link
- Fetch full job alongside approval_info for all UI data

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

* fix: filter _MODULES from args, show 'workflow' for WAC approvals

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

* chore: remove deno template from approval/prompt SuspendDrawer

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

* fix: approval page form display + hide deno from approval script picker

- Fix form schema rendering on new approval page by wrapping flat
  WAC form schemas in { properties, order } for SchemaForm
- Hide deno from the approval step language picker in flow editor

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

* fix: remove deno from canHaveApproval in script_helpers.ts

The insert menu uses canHaveApproval() from script_helpers.ts via
FlowInputsQuick, not the displayLang function in FlowInputs.svelte.
Revert the unnecessary FlowInputs.svelte change.

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

* fix: return form schema and description in approval_info for classic flows

The approval_info endpoint was returning None for form_schema on
classic flows. Now fetches raw_flow to get suspend.resume_form
schema, hide_cancel, and the step's completed result for description.

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

* feat: inline Login component on approval page instead of redirect

Show the Login component directly on the approval page when
authentication is required. On successful login, reloads user
and approval info without navigating away.

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

* fix: show resume buttons for all users, not just owners

The resume_suspended endpoint handles authorization server-side,
so the frontend should always show the buttons. Remove isOwner
gate and the "cannot resume" message.

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

* fix: prevent layout shift on resume by removing spinner from cancel button

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

* fix: prevent resume button expansion by using disabled instead of loading

The loading prop adds a Loader2 spinner that expands the button width.
Use disabled={loading} instead to prevent layout shift.

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

* fix: approval page login redirects back with full page reload

Set rd to the full URL (starts with http) so Login.redirectUser()
uses window.location.href instead of goto(), triggering a full page
reload after login. This ensures the approval page re-fetches data
as an authenticated user.

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

* fix: fetch flow definition from flow_version when raw_flow is null

Deployed flows don't store raw_flow on the job. Fall back to
flow_version table using runnable_id to get suspend settings
(form schema, hide_cancel) for the approval_info endpoint.

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

* feat: show specific reasons when user cannot approve

Display whether denial is due to self-approval being disabled,
required group membership, or both.

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

* feat: support both nested and flat form schema in waitForApproval

Users can now pass either:
  waitForApproval({ form: { schema: { name: { type: "string" } } } })
or:
  waitForApproval({ form: { name: { type: "string" } } })

Both WorkflowTimeline and approval page handle both formats.

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

* fix: convert sqlx query macros to non-macro for CI offline cache

Replace sqlx::query! and sqlx::query_scalar! with sqlx::query and
sqlx::query_as to avoid SQLX_OFFLINE cache misses in CI.
Also remove unused LogIn import from approval page.

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

* fix: suppress dead code warning + unused isOwner variable

- Add #[allow(dead_code)] to without_flow method (CI -D warnings)
- Rename isOwner to _isOwner in FlowStatusWaitingForEvents (unused)

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

* fix: security and robustness fixes from PR review

- Add workspace_id verification in resume_suspended to prevent
  cross-workspace approval (#3)
- Fix token leakage: use relative path for login redirect instead
  of full URL with token (#4)
- Handle getJob failure independently from approval_info so the
  page works for unauthenticated users (#7)
- Clear error state on successful data load (#13)

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

* fix: address review feedback — shared token gen, rand resume_id, UX

- Move generate_approval_token to windmill-common::variables (shared
  between windmill-api and windmill-worker, eliminates duplicate HMAC)
- Use rand::random::<u32>() for resume_id instead of DefaultHasher
- Stop polling after approve/reject on approval page
- Add cancelLoading state to WorkflowTimeline Reject button

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:22:35 +00:00

1685 lines
48 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'
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'
-- $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'
-- :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'
-- @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'
-- ? 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'
-- @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, 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, 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: "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, 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, 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 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,
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, 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, 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' => [...]]
//
// 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
# docker
# The annotation "docker" above is important, it tells windmill that after
# the end of the bash script, it should manage the container at id $WM_JOB_ID:
# pipe logs, monitor memory usage, kill container if job is cancelled.
msg="\${1:-world}"
IMAGE="alpine:latest"
COMMAND="/bin/echo Hello $msg"
# ensure that the image is up-to-date
docker pull $IMAGE
# if using the 'docker' mode, name it with $WM_JOB_ID for windmill to monitor it
docker run --name $WM_JOB_ID -it -d $IMAGE $COMMAND
`
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
`
// 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
},
claudesandbox: {
script: CLAUDE_SANDBOX_INIT_CODE
},
wac_python: {
script: WAC_PYTHON_INIT_CODE
},
wac_typescript: {
script: WAC_TYPESCRIPT_INIT_CODE
}
// for related places search: ADD_NEW_LANG
}
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'
| 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 === '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
// 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'
| 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'
}
}