Merge remote-tracking branch 'origin/main' into fork-datatable-schema-export

This commit is contained in:
Diego Imbert
2026-03-12 13:03:23 +01:00
48 changed files with 821 additions and 94 deletions
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT (config->>'native_mode')::boolean FROM config WHERE name = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "bool",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "36b95bc7956eb7bba7cd6fa9cd829980a0bf4970b919cabad1daab16627404fc"
}
+3 -8
View File
@@ -20,6 +20,7 @@ pub async fn connect_db(
server_mode: bool,
indexer_mode: bool,
worker_mode: bool,
num_workers: i32,
#[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> anyhow::Result<sqlx::Pool<sqlx::Postgres>> {
use anyhow::Context;
@@ -34,13 +35,7 @@ pub async fn connect_db(
} else if indexer_mode {
DEFAULT_MAX_CONNECTIONS_INDEXER
} else {
DEFAULT_MAX_CONNECTIONS_WORKER
+ std::env::var("NUM_WORKERS")
.ok()
.map(|x| x.parse().ok())
.flatten()
.unwrap_or(1)
- 1
DEFAULT_MAX_CONNECTIONS_WORKER + (num_workers.max(1) as u32) - 1
}
}
};
@@ -103,7 +98,7 @@ pub async fn connect(
use sqlx::Executor;
use std::time::Duration;
let mut pool_options = sqlx::postgres::PgPoolOptions::new()
.min_connections((max_connections / 5).clamp(1, max_connections))
.min_connections(0)
.max_connections(max_connections)
.max_lifetime(Duration::from_secs(30 * 60)); // 30 mins
if worker_mode {
+27 -11
View File
@@ -694,6 +694,7 @@ async fn windmill_main() -> anyhow::Result<()> {
let mut num_workers = if mode == Mode::Server || mode == Mode::Indexer || mode == Mode::MCP {
0
} else if is_native_mode_from_env() {
NATIVE_MODE_RESOLVED.store(true, std::sync::atomic::Ordering::Relaxed);
println!("Native mode enabled: forcing NUM_WORKERS=8");
8
} else {
@@ -866,6 +867,30 @@ async fn windmill_main() -> anyhow::Result<()> {
}
}
// Resolve native mode early (before connect_db) so connection pool size accounts for it.
// native_mode can come from env OR from the DB worker group config.
if worker_mode && !is_native_mode_from_env() {
if let Some(db) = conn.as_sql() {
let native_from_db: bool = sqlx::query_scalar!(
"SELECT (config->>'native_mode')::boolean FROM config WHERE name = $1",
format!("worker__{}", *windmill_common::worker::WORKER_GROUP)
)
.fetch_optional(db)
.await
.ok()
.flatten()
.flatten()
.unwrap_or(false);
if native_from_db {
NATIVE_MODE_RESOLVED.store(true, std::sync::atomic::Ordering::Relaxed);
num_workers = 8;
tracing::info!(
"Native mode detected from worker config (early): forcing NUM_WORKERS=8"
);
}
}
}
let conn = if mode == Mode::Agent {
conn
} else {
@@ -878,6 +903,7 @@ async fn windmill_main() -> anyhow::Result<()> {
server_mode,
indexer_mode,
worker_mode,
num_workers,
#[cfg(feature = "private")]
killpill_rx.resubscribe(),
)
@@ -982,16 +1008,6 @@ Windmill Community Edition {GIT_VERSION}
)
.await;
// native_mode may also be set via DB worker group config (not just env).
// NATIVE_MODE_RESOLVED is updated by load_worker_config during initial_load.
if worker_mode
&& !is_native_mode_from_env()
&& NATIVE_MODE_RESOLVED.load(std::sync::atomic::Ordering::Relaxed)
{
num_workers = 8;
tracing::info!("Native mode detected from worker config: forcing NUM_WORKERS=8");
}
monitor_db(
&conn,
&base_internal_url,
@@ -1884,7 +1900,7 @@ pub async fn run_workers(
tracing::info!(
"Starting {num_workers} workers and SLEEP_QUEUE={}ms",
*windmill_worker::SLEEP_QUEUE
windmill_worker::sleep_queue()
);
for i in 1..(num_workers + 1) {
+1 -2
View File
@@ -251,9 +251,8 @@ pub async fn initial_load(
.map(|x| x.tags.clone())
.unwrap_or_default();
// we only check from env as native_mode is not stored in the token
// NATIVE_MODE_RESOLVED is already set in main.rs during startup
let native_mode = windmill_common::worker::is_native_mode_from_env();
windmill_common::worker::NATIVE_MODE_RESOLVED
.store(native_mode, std::sync::atomic::Ordering::Relaxed);
*config = WorkerConfig {
worker_tags,
env_vars: load_env_vars(
+3 -2
View File
@@ -129,11 +129,12 @@ async fn update_config(
#[cfg(not(feature = "enterprise"))]
let config = if name.starts_with("worker__") {
// In CE, only allow setting worker_tags, cache_clear, and init_bash
// In CE, only allow setting worker_tags, cache_clear, init_bash, and native_mode
serde_json::json!({
"worker_tags": config.get("worker_tags"),
"cache_clear": config.get("cache_clear"),
"init_bash": config.get("init_bash")
"init_bash": config.get("init_bash"),
"native_mode": config.get("native_mode")
})
} else {
config
+13 -5
View File
@@ -303,7 +303,7 @@ pub struct PowershellRepo {
lazy_static::lazy_static! {
pub static ref SLEEP_QUEUE: u64 = std::env::var("SLEEP_QUEUE")
static ref SLEEP_QUEUE_BASE: u64 = std::env::var("SLEEP_QUEUE")
.ok()
.and_then(|x| x.parse::<u64>().ok())
.unwrap_or_else(|| {
@@ -647,6 +647,14 @@ lazy_static::lazy_static! {
pub static ref FLOW_RUNNER_RUNNING: Mutex<bool> = Mutex::new(false);
}
pub fn sleep_queue() -> u64 {
if NATIVE_MODE_RESOLVED.load(std::sync::atomic::Ordering::Relaxed) {
300
} else {
*SLEEP_QUEUE_BASE
}
}
type Envs = Vec<(String, String)>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -1373,7 +1381,7 @@ fn start_interactive_worker_shell(
{
Duration::from_secs(WORKER_SHELL_NAP_TIME_DURATION)
}
_ => Duration::from_millis(*SLEEP_QUEUE * 10),
_ => Duration::from_millis(sleep_queue() * 10),
};
tokio::select! {
_ = tokio::time::sleep(nap_time) => {
@@ -1386,7 +1394,7 @@ fn start_interactive_worker_shell(
Err(err) => {
tracing::error!(worker = %worker_name, hostname = %hostname, "Failed to pull jobs: {}", err);
tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 20)).await;
tokio::time::sleep(Duration::from_millis(sleep_queue() * 20)).await;
}
};
}
@@ -2699,7 +2707,7 @@ pub async fn run_worker(
None
};
tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE)).await;
tokio::time::sleep(Duration::from_millis(sleep_queue())).await;
#[cfg(feature = "benchmark")]
{
@@ -2720,7 +2728,7 @@ pub async fn run_worker(
}
Err(err) => {
tracing::error!(worker = %worker_name, hostname = %hostname, "Failed to pull jobs: {}", err);
tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE * 5)).await;
tokio::time::sleep(Duration::from_millis(sleep_queue() * 5)).await;
}
};
}
+16 -2
View File
@@ -56,13 +56,20 @@ export async function pushFlow(
}
const localFlow = (await yamlParseFile(localPath + "flow.yaml")) as FlowFile;
const fileReader = async (path: string) => await readFile(localPath + path, "utf-8");
await replaceInlineScripts(
localFlow.value.modules,
async (path: string) => await readFile(localPath + path, "utf-8"),
fileReader,
log,
localPath,
SEP
);
if (localFlow.value.failure_module) {
await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, localPath, SEP);
}
if (localFlow.value.preprocessor_module) {
await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, localPath, SEP);
}
if (flow) {
if (isSuperset(localFlow, flow)) {
@@ -252,13 +259,20 @@ async function preview(
const localFlow = (await yamlParseFile(flowPath + "flow.yaml")) as FlowFile;
// Replace inline scripts with their actual content
const fileReader = async (path: string) => await readFile(flowPath + path, "utf-8");
await replaceInlineScripts(
localFlow.value.modules,
async (path: string) => await readFile(flowPath + path, "utf-8"),
fileReader,
log,
flowPath,
SEP
);
if (localFlow.value.failure_module) {
await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, flowPath, SEP);
}
if (localFlow.value.preprocessor_module) {
await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, flowPath, SEP);
}
const input = opts.data ? await resolve(opts.data) : {};
+27 -3
View File
@@ -19,6 +19,7 @@ import {
} from "../../utils/metadata.ts";
import { ScriptLanguage } from "../../utils/script_common.ts";
import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts";
import { newPathAssigner } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts";
import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.ts";
import { exts } from "../script/script.ts";
@@ -121,14 +122,21 @@ export async function generateFlowLockInternal(
}
log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
const fileReader = async (path: string) => await readFile(folder + SEP + path, "utf-8");
await replaceInlineScripts(
flowValue.value.modules,
async (path: string) => await readFile(folder + SEP + path, "utf-8"),
fileReader,
log,
folder + SEP!,
SEP,
changedScripts
);
if (flowValue.value.failure_module) {
await replaceInlineScripts([flowValue.value.failure_module], fileReader, log, folder + SEP!, SEP, changedScripts);
}
if (flowValue.value.preprocessor_module) {
await replaceInlineScripts([flowValue.value.preprocessor_module], fileReader, log, folder + SEP!, SEP, changedScripts);
}
//removeChangedLocks
flowValue.value = await updateFlow(
@@ -138,12 +146,20 @@ export async function generateFlowLockInternal(
filteredDeps
);
const lockAssigner = newPathAssigner(opts.defaultTs ?? "bun");
const inlineScripts = extractInlineScriptsForFlows(
flowValue.value.modules,
{},
SEP,
opts.defaultTs
opts.defaultTs,
lockAssigner
);
if (flowValue.value.failure_module) {
inlineScripts.push(...extractInlineScriptsForFlows([flowValue.value.failure_module], {}, SEP, opts.defaultTs, lockAssigner));
}
if (flowValue.value.preprocessor_module) {
inlineScripts.push(...extractInlineScriptsForFlows([flowValue.value.preprocessor_module], {}, SEP, opts.defaultTs, lockAssigner));
}
inlineScripts.forEach((s) => {
writeIfChanged(process.cwd() + SEP + folder + SEP + s.path, s.content);
});
@@ -176,7 +192,15 @@ async function filterWorkspaceDependenciesForFlow(
rawWorkspaceDependencies: Record<string, string>,
folder: string
): Promise<Record<string, string>> {
const inlineScripts = extractInlineScriptsForFlows(structuredClone(flowValue.modules), {}, SEP, undefined);
const clonedValue = structuredClone(flowValue);
const depAssigner = newPathAssigner("bun");
const inlineScripts = extractInlineScriptsForFlows(clonedValue.modules, {}, SEP, undefined, depAssigner);
if (clonedValue.failure_module) {
inlineScripts.push(...extractInlineScriptsForFlows([clonedValue.failure_module], {}, SEP, undefined, depAssigner));
}
if (clonedValue.preprocessor_module) {
inlineScripts.push(...extractInlineScriptsForFlows([clonedValue.preprocessor_module], {}, SEP, undefined, depAssigner));
}
// Filter out lock files and map to common interface
const scripts = inlineScripts
+22 -1
View File
@@ -592,14 +592,35 @@ function ZipFSElement(
}
let inlineScripts;
try {
const assigner = newPathAssigner(defaultTs, { skipInlineScriptSuffix: getNonDottedPaths() });
inlineScripts = extractInlineScriptsForFlows(
flow.value.modules as any,
{},
SEP,
defaultTs,
undefined, // pathAssigner - let it create one
assigner,
{ skipInlineScriptSuffix: getNonDottedPaths() },
);
if (flow.value.failure_module) {
inlineScripts.push(...extractInlineScriptsForFlows(
[flow.value.failure_module],
{},
SEP,
defaultTs,
assigner,
{ skipInlineScriptSuffix: getNonDottedPaths() },
));
}
if (flow.value.preprocessor_module) {
inlineScripts.push(...extractInlineScriptsForFlows(
[flow.value.preprocessor_module],
{},
SEP,
defaultTs,
assigner,
{ skipInlineScriptSuffix: getNonDottedPaths() },
));
}
} catch (error) {
log.error(
`Failed to extract inline scripts for flow at path: ${p}`,
+77 -24
View File
@@ -43,10 +43,12 @@ description: MUST use when writing Go scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Go
@@ -115,10 +117,12 @@ description: MUST use when writing Java scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Java
@@ -167,10 +171,12 @@ description: MUST use when writing GraphQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# GraphQL
@@ -226,10 +232,12 @@ description: MUST use when writing Rust scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Rust
@@ -315,10 +323,12 @@ description: MUST use when writing Bun Native scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# TypeScript (Bun Native)
@@ -967,10 +977,12 @@ description: MUST use when writing PostgreSQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# PostgreSQL
@@ -992,10 +1004,12 @@ description: MUST use when writing PHP scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# PHP
@@ -1063,10 +1077,12 @@ description: MUST use when writing BigQuery queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# BigQuery
@@ -1088,10 +1104,12 @@ description: MUST use when writing Bun/TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# TypeScript (Bun)
@@ -1742,10 +1760,12 @@ description: MUST use when writing C# scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# C#
@@ -1797,10 +1817,12 @@ description: MUST use when writing MS SQL Server queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Microsoft SQL Server (MSSQL)
@@ -1822,10 +1844,12 @@ description: MUST use when writing Deno/TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# TypeScript (Deno)
@@ -2480,10 +2504,12 @@ description: MUST use when writing MySQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# MySQL
@@ -2505,10 +2531,12 @@ description: MUST use when writing PowerShell scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# PowerShell
@@ -2574,10 +2602,12 @@ description: MUST use when writing Snowflake queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Snowflake
@@ -2599,10 +2629,12 @@ description: MUST use when writing Python scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Python
@@ -3404,10 +3436,12 @@ description: MUST use when writing DuckDB queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# DuckDB
@@ -3469,10 +3503,12 @@ description: MUST use when writing Bash scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# Bash
@@ -3532,10 +3568,12 @@ description: MUST use when writing Native TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- \`wmill script generate-metadata\` - Generate .script.yaml and .lock files
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use \`wmill resource-type list --schema\` to discover available resource types.
# TypeScript (Native)
@@ -4155,10 +4193,12 @@ description: MUST use when creating flows.
Create a folder ending with \`.flow\` and add a YAML file with the flow definition.
For rawscript modules, use \`!inline path/to/script.ts\` for the content key.
After writing:
After writing, tell the user they can run:
- \`wmill flow generate-locks <path_to_flow_folder> --yes\` - Generate lock files for the specific flow you modified (e.g. \`wmill flow generate-locks f/my_folder/my_flow.flow --yes\`)
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
## OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
@@ -4356,7 +4396,7 @@ export async function main(user_id: string) {
}
\`\`\`
After creating, generate lock files:
After creating, tell the user they can generate lock files by running:
\`\`\`bash
wmill app generate-locks
\`\`\`
@@ -4509,6 +4549,8 @@ data:
## CLI Commands
Tell the user they can run these commands (do NOT run them yourself):
| Command | Description |
|---------|-------------|
| \`wmill app new\` | Create a new raw app interactively |
@@ -4525,7 +4567,7 @@ data:
3. **Keep runnables focused** - one function per file
4. **Use descriptive IDs** - \`get_user.ts\` not \`a.ts\`
5. **Always whitelist tables** - add to \`data.tables\` before querying
6. **Generate locks** - run \`wmill app generate-locks\` after adding/modifying backend runnables
6. **Generate locks** - tell the user to run \`wmill app generate-locks\` after adding/modifying backend runnables
`,
"triggers": `---
name: triggers
@@ -4547,6 +4589,8 @@ Examples:
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
\`\`\`bash
# Push trigger configuration
wmill sync push
@@ -4596,6 +4640,8 @@ Windmill uses 6-field cron expressions (includes seconds):
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
\`\`\`bash
# Push schedules to Windmill
wmill sync push
@@ -4851,7 +4897,7 @@ wmill resource-type list --schema
# Get specific resource type schema
wmill resource-type get postgresql
# Push resources
# Push resources (tell the user to run this, do NOT run it yourself)
wmill sync push
\`\`\`
`,
@@ -4864,7 +4910,7 @@ description: MUST use when using the CLI.
The Windmill CLI (\`wmill\`) provides commands for managing scripts, flows, apps, and other resources.
Current version: 1.651.1
Current version: 1.653.0
## Global Options
@@ -5613,6 +5659,13 @@ properties:
key:
type: string
value: {}
auto_offset_reset:
type: string
enum:
- latest
- earliest
description: Initial offset behavior when consumer group has no committed offset.
'latest' starts from new messages only, 'earliest' starts from the beginning.
error_handler_path:
type: string
description: Path to a script or flow to run when the triggered job fails
@@ -0,0 +1,498 @@
/**
* Unit tests for failure_module and preprocessor_module inline script
* extraction (pull) and replacement (push).
*
* These tests verify that rawscript content in failure_module and
* preprocessor_module is correctly extracted to !inline references
* and resolved back, matching the existing behavior for regular modules.
*/
import { expect, test, describe } from "bun:test";
import { extractInlineScripts, extractCurrentMapping } from "../windmill-utils-internal/src/inline-scripts/extractor.ts";
import { replaceInlineScripts } from "../windmill-utils-internal/src/inline-scripts/replacer.ts";
import { newPathAssigner } from "../windmill-utils-internal/src/path-utils/path-assigner.ts";
import type { FlowModule } from "../windmill-utils-internal/src/gen/types.gen.ts";
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeRawscriptModule(
id: string,
content: string,
language: "bun" | "python3" | "deno" = "bun",
lock?: string,
): FlowModule {
return {
id,
value: {
type: "rawscript" as const,
content,
language,
lock: lock,
input_transforms: {},
},
};
}
const noopLogger = {
info: () => {},
error: () => {},
};
// ---------------------------------------------------------------------------
// extractInlineScripts — PULL direction
// ---------------------------------------------------------------------------
describe("extractInlineScripts for failure_module / preprocessor_module", () => {
test("extracts rawscript from failure_module wrapped in array", () => {
const failureModule = makeRawscriptModule(
"failure",
'export function main() { throw new Error("handler"); }',
"bun",
);
const scripts = extractInlineScripts([failureModule], {}, "/", "bun");
expect(scripts.length).toBeGreaterThanOrEqual(1);
const script = scripts.find((s) => !s.is_lock);
expect(script).toBeDefined();
expect(script!.content).toBe(
'export function main() { throw new Error("handler"); }',
);
// The module content should have been replaced with an !inline reference
expect(failureModule.value.content).toStartWith("!inline ");
});
test("extracts rawscript from preprocessor_module wrapped in array", () => {
const preprocessorModule = makeRawscriptModule(
"preprocessor",
"export function main() { return {}; }",
"python3",
);
const scripts = extractInlineScripts(
[preprocessorModule],
{},
"/",
"bun",
);
expect(scripts.length).toBeGreaterThanOrEqual(1);
const script = scripts.find((s) => !s.is_lock);
expect(script).toBeDefined();
expect(script!.content).toBe("export function main() { return {}; }");
expect(script!.language).toBe("python3");
expect(preprocessorModule.value.content).toStartWith("!inline ");
});
test("extracts lock alongside content", () => {
const mod = makeRawscriptModule(
"failure",
"console.log('hi')",
"bun",
"some-lock-content",
);
const scripts = extractInlineScripts([mod], {}, "/", "bun");
const contentScript = scripts.find((s) => !s.is_lock);
const lockScript = scripts.find((s) => s.is_lock);
expect(contentScript).toBeDefined();
expect(lockScript).toBeDefined();
expect(lockScript!.content).toBe("some-lock-content");
expect((mod.value as any).lock).toStartWith("!inline ");
});
test("shared pathAssigner prevents collisions when summaries match", () => {
// If a regular module and failure_module share the same summary,
// a shared PathAssigner deduplicates via its internal counter.
const regular = makeRawscriptModule("a", "code_a", "bun");
regular.summary = "my step";
const failure = makeRawscriptModule("failure", "code_failure", "bun");
failure.summary = "my step"; // same summary — would collide without shared assigner
const assigner = newPathAssigner("bun");
const scripts1 = extractInlineScripts([regular], {}, "/", "bun", assigner);
const scripts2 = extractInlineScripts([failure], {}, "/", "bun", assigner);
const allPaths = [...scripts1, ...scripts2]
.filter((s) => !s.is_lock)
.map((s) => s.path);
// All paths should be unique despite identical summaries
expect(allPaths.length).toBe(2);
expect(new Set(allPaths).size).toBe(2);
});
test("without shared pathAssigner, identical summaries produce duplicate paths", () => {
// Demonstrates the problem that sharing a PathAssigner solves.
const regular = makeRawscriptModule("a", "code_a", "bun");
regular.summary = "my step";
const failure = makeRawscriptModule("failure", "code_failure", "bun");
failure.summary = "my step";
// Separate assigners — each starts with a fresh counter
const scripts1 = extractInlineScripts([regular], {}, "/", "bun");
const scripts2 = extractInlineScripts([failure], {}, "/", "bun");
const allPaths = [...scripts1, ...scripts2]
.filter((s) => !s.is_lock)
.map((s) => s.path);
// Without a shared assigner, the paths collide
expect(allPaths.length).toBe(2);
expect(new Set(allPaths).size).toBe(1); // both got the same path
});
test("skips non-rawscript failure_module (identity type)", () => {
const identityModule: FlowModule = {
id: "failure",
value: { type: "identity" as any },
};
const scripts = extractInlineScripts([identityModule], {}, "/", "bun");
expect(scripts).toEqual([]);
});
});
// ---------------------------------------------------------------------------
// replaceInlineScripts — PUSH direction
// ---------------------------------------------------------------------------
describe("replaceInlineScripts for failure_module / preprocessor_module", () => {
test("resolves !inline reference back to file content", async () => {
const failureModule = makeRawscriptModule(
"failure",
"!inline failure.inline_script.ts",
"bun",
);
const files: Record<string, string> = {
"failure.inline_script.ts": 'export function main() { return "error handled"; }',
};
await replaceInlineScripts(
[failureModule],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(failureModule.value.content).toBe(
'export function main() { return "error handled"; }',
);
});
test("resolves !inline reference for preprocessor_module", async () => {
const preprocessorModule = makeRawscriptModule(
"preprocessor",
"!inline preprocessor.inline_script.py",
"python3",
);
const files: Record<string, string> = {
"preprocessor.inline_script.py": "def main(): return {}",
};
await replaceInlineScripts(
[preprocessorModule],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(preprocessorModule.value.content).toBe("def main(): return {}");
});
test("resolves !inline lock reference", async () => {
const mod = makeRawscriptModule(
"failure",
"!inline failure.inline_script.ts",
"bun",
"!inline failure.inline_script.lock",
);
const files: Record<string, string> = {
"failure.inline_script.ts": "code here",
"failure.inline_script.lock": "lock-data-here",
};
await replaceInlineScripts(
[mod],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(mod.value.content).toBe("code here");
expect((mod.value as any).lock).toBe("lock-data-here");
});
test("leaves non-inline content untouched", async () => {
const mod = makeRawscriptModule(
"failure",
"export function main() { return 1; }",
"bun",
);
await replaceInlineScripts(
[mod],
async () => {
throw new Error("should not be called");
},
noopLogger,
"/tmp/test/",
"/",
);
expect(mod.value.content).toBe("export function main() { return 1; }");
});
});
// ---------------------------------------------------------------------------
// Round-trip: extract then replace
// ---------------------------------------------------------------------------
describe("round-trip extract → replace for failure_module / preprocessor_module", () => {
test("failure_module content survives extract + replace", async () => {
const originalContent = 'export function main(error: any) {\n console.error(error);\n return { handled: true };\n}';
const failureModule = makeRawscriptModule(
"failure",
originalContent,
"bun",
);
// PULL: extract inline scripts (mutates module in place)
const extracted = extractInlineScripts([failureModule], {}, "/", "bun");
expect(failureModule.value.content).toStartWith("!inline ");
// Build a virtual filesystem from extracted scripts
const files: Record<string, string> = {};
for (const s of extracted) {
files[s.path] = s.content;
}
// PUSH: replace inline references back
await replaceInlineScripts(
[failureModule],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(failureModule.value.content).toBe(originalContent);
});
test("preprocessor_module content survives extract + replace", async () => {
const originalContent = "def main():\n return {\"preprocessed\": True}";
const preprocessorModule = makeRawscriptModule(
"preprocessor",
originalContent,
"python3",
);
const extracted = extractInlineScripts(
[preprocessorModule],
{},
"/",
"bun",
);
expect(preprocessorModule.value.content).toStartWith("!inline ");
const files: Record<string, string> = {};
for (const s of extracted) {
files[s.path] = s.content;
}
await replaceInlineScripts(
[preprocessorModule],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(preprocessorModule.value.content).toBe(originalContent);
});
test("failure_module with lock survives extract + replace", async () => {
const originalContent = "export function main() { return 42; }";
const originalLock = "package-lock-contents-here";
const mod = makeRawscriptModule(
"failure",
originalContent,
"bun",
originalLock,
);
const extracted = extractInlineScripts([mod], {}, "/", "bun");
const files: Record<string, string> = {};
for (const s of extracted) {
files[s.path] = s.content;
}
await replaceInlineScripts(
[mod],
async (path) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
},
noopLogger,
"/tmp/test/",
"/",
);
expect(mod.value.content).toBe(originalContent);
expect((mod.value as any).lock).toBe(originalLock);
});
test("full flow with modules + failure_module + preprocessor_module round-trips", async () => {
const regularContent = "export function main() { return 'step1'; }";
const failureContent = "export function main(e: any) { return e; }";
const preprocessorContent = "def main():\n pass";
const modules = [makeRawscriptModule("a", regularContent, "bun")];
const failureModule = makeRawscriptModule("failure", failureContent, "bun");
const preprocessorModule = makeRawscriptModule("preprocessor", preprocessorContent, "python3");
// Extract all (mimicking sync.ts pull logic)
const allExtracted = [
...extractInlineScripts(modules, {}, "/", "bun"),
...extractInlineScripts([failureModule], {}, "/", "bun"),
...extractInlineScripts([preprocessorModule], {}, "/", "bun"),
];
// All modules should now have !inline references
expect(modules[0].value.content).toStartWith("!inline ");
expect(failureModule.value.content).toStartWith("!inline ");
expect(preprocessorModule.value.content).toStartWith("!inline ");
// All paths should be unique
const paths = allExtracted.filter((s) => !s.is_lock).map((s) => s.path);
expect(new Set(paths).size).toBe(paths.length);
// Build filesystem
const files: Record<string, string> = {};
for (const s of allExtracted) {
files[s.path] = s.content;
}
const fileReader = async (path: string) => {
if (!(path in files)) throw new Error(`File not found: ${path}`);
return files[path];
};
// Replace all (mimicking flow.ts push logic)
await replaceInlineScripts(modules, fileReader, noopLogger, "/tmp/", "/");
await replaceInlineScripts([failureModule], fileReader, noopLogger, "/tmp/", "/");
await replaceInlineScripts([preprocessorModule], fileReader, noopLogger, "/tmp/", "/");
expect(modules[0].value.content).toBe(regularContent);
expect(failureModule.value.content).toBe(failureContent);
expect(preprocessorModule.value.content).toBe(preprocessorContent);
});
});
// ---------------------------------------------------------------------------
// extractCurrentMapping
// ---------------------------------------------------------------------------
describe("extractCurrentMapping for failure_module / preprocessor_module", () => {
test("extracts mapping from failure_module via optional param", () => {
const failureModule: FlowModule = makeRawscriptModule(
"failure",
"!inline failure.inline_script.ts",
"bun",
);
const mapping = extractCurrentMapping(
undefined,
{},
failureModule,
undefined,
);
expect(mapping["failure"]).toBe("failure.inline_script.ts");
});
test("extracts mapping from preprocessor_module via optional param", () => {
const preprocessorModule: FlowModule = makeRawscriptModule(
"preprocessor",
"!inline preprocessor.inline_script.py",
"python3",
);
const mapping = extractCurrentMapping(
undefined,
{},
undefined,
preprocessorModule,
);
expect(mapping["preprocessor"]).toBe("preprocessor.inline_script.py");
});
test("extracts mapping from modules + failure + preprocessor combined", () => {
const modules: FlowModule[] = [
makeRawscriptModule("a", "!inline a.inline_script.ts", "bun"),
];
const failureModule = makeRawscriptModule(
"failure",
"!inline failure.inline_script.ts",
"bun",
);
const preprocessorModule = makeRawscriptModule(
"preprocessor",
"!inline preprocessor.inline_script.py",
"python3",
);
const mapping = extractCurrentMapping(
modules,
{},
failureModule,
preprocessorModule,
);
expect(mapping["a"]).toBe("a.inline_script.ts");
expect(mapping["failure"]).toBe("failure.inline_script.ts");
expect(mapping["preprocessor"]).toBe("preprocessor.inline_script.py");
});
test("ignores non-inline content in failure_module", () => {
const failureModule = makeRawscriptModule(
"failure",
"export function main() {}",
"bun",
);
const mapping = extractCurrentMapping(
undefined,
{},
failureModule,
undefined,
);
expect(mapping["failure"]).toBeUndefined();
});
});
@@ -150,8 +150,17 @@ export function extractInlineScripts(
*/
export function extractCurrentMapping(
modules: FlowModule[] | undefined,
mapping: Record<string, string> = {}
mapping: Record<string, string> = {},
failureModule?: FlowModule,
preprocessorModule?: FlowModule,
): Record<string, string> {
if (failureModule) {
extractCurrentMapping([failureModule], mapping);
}
if (preprocessorModule) {
extractCurrentMapping([preprocessorModule], mapping);
}
if (!modules || !Array.isArray(modules)) {
return mapping;
}
@@ -102,8 +102,8 @@
})
}
if (defaultLang !== undefined) {
setupModel(defaultLang, defaultOriginal, defaultModified, defaultModifiedLang)
if (defaultLang !== undefined || defaultOriginal !== undefined || defaultModified !== undefined) {
setupModel(defaultLang ?? 'plaintext', defaultOriginal, defaultModified, defaultModifiedLang)
}
}
@@ -2,7 +2,7 @@
The Windmill CLI (`wmill`) provides commands for managing scripts, flows, apps, and other resources.
Current version: 1.651.1
Current version: 1.653.0
## Global Options
+3 -1
View File
@@ -4,10 +4,12 @@
Create a folder ending with `.flow` and add a YAML file with the flow definition.
For rawscript modules, use `!inline path/to/script.ts` for the content key.
After writing:
After writing, tell the user they can run:
- `wmill flow generate-locks <path_to_flow_folder> --yes` - Generate lock files for the specific flow you modified (e.g. `wmill flow generate-locks f/my_folder/my_flow.flow --yes`)
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
## OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
+4 -2
View File
@@ -35,10 +35,12 @@ export const FLOW_BASE = `# Windmill Flow Building Guide
Create a folder ending with \`.flow\` and add a YAML file with the flow definition.
For rawscript modules, use \`!inline path/to/script.ts\` for the content key.
After writing:
After writing, tell the user they can run:
- \`wmill flow generate-locks <path_to_flow_folder> --yes\` - Generate lock files for the specific flow you modified (e.g. \`wmill flow generate-locks f/my_folder/my_flow.flow --yes\`)
- \`wmill sync push\` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
## OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
@@ -1356,7 +1358,7 @@ export const CLI_COMMANDS = `# Windmill CLI Commands
The Windmill CLI (\`wmill\`) provides commands for managing scripts, flows, apps, and other resources.
Current version: 1.651.1
Current version: 1.653.0
## Global Options
@@ -25,6 +25,13 @@ properties:
key:
type: string
value: {}
auto_offset_reset:
type: string
enum:
- latest
- earliest
description: Initial offset behavior when consumer group has no committed offset.
'latest' starts from new messages only, 'earliest' starts from the beginning.
error_handler_path:
type: string
description: Path to a script or flow to run when the triggered job fails
@@ -7,7 +7,7 @@ description: MUST use when using the CLI.
The Windmill CLI (`wmill`) provides commands for managing scripts, flows, apps, and other resources.
Current version: 1.651.1
Current version: 1.653.0
## Global Options
@@ -84,7 +84,7 @@ export async function main(user_id: string) {
}
```
After creating, generate lock files:
After creating, tell the user they can generate lock files by running:
```bash
wmill app generate-locks
```
@@ -237,6 +237,8 @@ data:
## CLI Commands
Tell the user they can run these commands (do NOT run them yourself):
| Command | Description |
|---------|-------------|
| `wmill app new` | Create a new raw app interactively |
@@ -253,4 +255,4 @@ data:
3. **Keep runnables focused** - one function per file
4. **Use descriptive IDs** - `get_user.ts` not `a.ts`
5. **Always whitelist tables** - add to `data.tables` before querying
6. **Generate locks** - run `wmill app generate-locks` after adding/modifying backend runnables
6. **Generate locks** - tell the user to run `wmill app generate-locks` after adding/modifying backend runnables
@@ -242,6 +242,6 @@ wmill resource-type list --schema
# Get specific resource type schema
wmill resource-type get postgresql
# Push resources
# Push resources (tell the user to run this, do NOT run it yourself)
wmill sync push
```
@@ -39,6 +39,8 @@ Windmill uses 6-field cron expressions (includes seconds):
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
```bash
# Push schedules to Windmill
wmill sync push
@@ -18,6 +18,8 @@ Examples:
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
```bash
# Push trigger configuration
wmill sync push
@@ -9,10 +9,12 @@ description: MUST use when creating flows.
Create a folder ending with `.flow` and add a YAML file with the flow definition.
For rawscript modules, use `!inline path/to/script.ts` for the content key.
After writing:
After writing, tell the user they can run:
- `wmill flow generate-locks <path_to_flow_folder> --yes` - Generate lock files for the specific flow you modified (e.g. `wmill flow generate-locks f/my_folder/my_flow.flow --yes`)
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
## OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
@@ -5,10 +5,12 @@ description: MUST use when writing Bash scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Bash
@@ -5,10 +5,12 @@ description: MUST use when writing BigQuery queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# BigQuery
@@ -5,10 +5,12 @@ description: MUST use when writing Bun/TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# TypeScript (Bun)
@@ -5,10 +5,12 @@ description: MUST use when writing Bun Native scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# TypeScript (Bun Native)
@@ -5,10 +5,12 @@ description: MUST use when writing C# scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# C#
@@ -5,10 +5,12 @@ description: MUST use when writing Deno/TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# TypeScript (Deno)
@@ -5,10 +5,12 @@ description: MUST use when writing DuckDB queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# DuckDB
@@ -5,10 +5,12 @@ description: MUST use when writing Go scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Go
@@ -5,10 +5,12 @@ description: MUST use when writing GraphQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# GraphQL
@@ -5,10 +5,12 @@ description: MUST use when writing Java scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Java
@@ -5,10 +5,12 @@ description: MUST use when writing MS SQL Server queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Microsoft SQL Server (MSSQL)
@@ -5,10 +5,12 @@ description: MUST use when writing MySQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# MySQL
@@ -5,10 +5,12 @@ description: MUST use when writing Native TypeScript scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# TypeScript (Native)
@@ -5,10 +5,12 @@ description: MUST use when writing PHP scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# PHP
@@ -5,10 +5,12 @@ description: MUST use when writing PostgreSQL queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# PostgreSQL
@@ -5,10 +5,12 @@ description: MUST use when writing PowerShell scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# PowerShell
@@ -5,10 +5,12 @@ description: MUST use when writing Python scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Python
@@ -5,10 +5,12 @@ description: MUST use when writing Rust scripts.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Rust
@@ -5,10 +5,12 @@ description: MUST use when writing Snowflake queries.
## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types.
# Snowflake
+3 -1
View File
@@ -4,10 +4,12 @@
Create a folder ending with `.flow` and add a YAML file with the flow definition.
For rawscript modules, use `!inline path/to/script.ts` for the content key.
After writing:
After writing, tell the user they can run:
- `wmill flow generate-locks <path_to_flow_folder> --yes` - Generate lock files for the specific flow you modified (e.g. `wmill flow generate-locks f/my_folder/my_flow.flow --yes`)
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
## OpenFlow Schema
The OpenFlow schema (openflow.openapi.yaml) is the source of truth for flow structure. Refer to OPENFLOW_SCHEMA for the complete type definitions.
+4 -2
View File
@@ -79,7 +79,7 @@ export async function main(user_id: string) {
}
```
After creating, generate lock files:
After creating, tell the user they can generate lock files by running:
```bash
wmill app generate-locks
```
@@ -232,6 +232,8 @@ data:
## CLI Commands
Tell the user they can run these commands (do NOT run them yourself):
| Command | Description |
|---------|-------------|
| `wmill app new` | Create a new raw app interactively |
@@ -248,4 +250,4 @@ data:
3. **Keep runnables focused** - one function per file
4. **Use descriptive IDs** - `get_user.ts` not `a.ts`
5. **Always whitelist tables** - add to `data.tables` before querying
6. **Generate locks** - run `wmill app generate-locks` after adding/modifying backend runnables
6. **Generate locks** - tell the user to run `wmill app generate-locks` after adding/modifying backend runnables
+1 -1
View File
@@ -237,6 +237,6 @@ wmill resource-type list --schema
# Get specific resource type schema
wmill resource-type get postgresql
# Push resources
# Push resources (tell the user to run this, do NOT run it yourself)
wmill sync push
```
+2
View File
@@ -34,6 +34,8 @@ Windmill uses 6-field cron expressions (includes seconds):
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
```bash
# Push schedules to Windmill
wmill sync push
+2
View File
@@ -13,6 +13,8 @@ Examples:
## CLI Commands
After writing, tell the user they can run these commands (do NOT run them yourself):
```bash
# Push trigger configuration
wmill sync push
+3 -1
View File
@@ -763,10 +763,12 @@ def generate_skills(
# CLI intro for script skills
script_cli_intro = """## CLI Commands
Place scripts in a folder. After writing, run:
Place scripts in a folder. After writing, tell the user they can run:
- `wmill script generate-metadata` - Generate .script.yaml and .lock files
- `wmill sync push` - Deploy to Windmill
Do NOT run these commands yourself. Instead, inform the user that they should run them.
Use `wmill resource-type list --schema` to discover available resource types."""
skills_generated = []