Compare commits

..

2 Commits

Author SHA1 Message Date
Ruben Fiszel
73f649c152 chore(main): release 1.668.3 (#8591)
* chore(main): release 1.668.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-03-28 21:04:44 +00:00
Ruben Fiszel
c6ce3197a7 fix(cli): phantom diffs, flow safety, trigger DX, lint watch, error clarity (#8588)
* fix(cli): phantom diffs, flow push safety, error messages, digest stability

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

* fix(cli): differentiate stale vs missing metadata warnings on script push

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

* fix(cli): job list --limit off-by-one, deps push double error

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

* fix(cli): flow get shows nested steps, lint works on specific directories

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

* feat(cli): add lint --watch mode for continuous validation

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

* fix(cli): email trigger template missing local_part, trigger get shows all fields

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

* fix(cli): fix CI — flow push warns instead of failing, lint subdir detection

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-28 20:56:10 +00:00
32 changed files with 319 additions and 266 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## [1.668.3](https://github.com/windmill-labs/windmill/compare/v1.668.2...v1.668.3) (2026-03-28)
### Bug Fixes
* **cli:** phantom diffs, flow safety, trigger DX, lint watch, error clarity ([#8588](https://github.com/windmill-labs/windmill/issues/8588)) ([c6ce319](https://github.com/windmill-labs/windmill/commit/c6ce3197a72ceeffd702cf2263b1074ecbf1ca33))
## [1.668.2](https://github.com/windmill-labs/windmill/compare/v1.668.1...v1.668.2) (2026-03-28)

154
backend/Cargo.lock generated
View File

@@ -7373,9 +7373,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]]
name = "inventory"
version = "0.3.22"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227"
checksum = "8062b737e5389949f477d4760a2ebbff0c366f97798f2419b8d8f366363d3342"
dependencies = [
"rustversion",
]
@@ -15813,7 +15813,7 @@ dependencies = [
[[package]]
name = "windmill"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-nats",
@@ -15891,7 +15891,7 @@ dependencies = [
[[package]]
name = "windmill-alerting"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -15904,7 +15904,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"argon2",
@@ -16045,7 +16045,7 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16068,7 +16068,7 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16081,7 +16081,7 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16107,7 +16107,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -16117,7 +16117,7 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16134,7 +16134,7 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"base64 0.22.1",
@@ -16157,7 +16157,7 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16180,7 +16180,7 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16196,7 +16196,7 @@ dependencies = [
[[package]]
name = "windmill-api-flows"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16216,7 +16216,7 @@ dependencies = [
[[package]]
name = "windmill-api-groups"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16236,7 +16236,7 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16250,7 +16250,7 @@ dependencies = [
[[package]]
name = "windmill-api-integration-tests"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-nats",
@@ -16280,7 +16280,7 @@ dependencies = [
[[package]]
name = "windmill-api-jobs"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16305,7 +16305,7 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"flate2",
@@ -16323,7 +16323,7 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16345,7 +16345,7 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16365,7 +16365,7 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16395,7 +16395,7 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16422,7 +16422,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"lazy_static",
"serde",
@@ -16434,7 +16434,7 @@ dependencies = [
[[package]]
name = "windmill-api-users"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"argon2",
"axum 0.8.4",
@@ -16458,7 +16458,7 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16472,7 +16472,7 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"axum 0.8.4",
"chrono",
@@ -16504,7 +16504,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"chrono",
"lazy_static",
@@ -16518,7 +16518,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"axum 0.8.4",
@@ -16537,7 +16537,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"aes-gcm",
"aho-corasick",
@@ -16638,7 +16638,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"chrono",
"itertools 0.14.0",
@@ -16657,7 +16657,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"regex",
"serde",
@@ -16672,7 +16672,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -16696,7 +16696,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"futures",
@@ -16713,7 +16713,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -16729,7 +16729,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -16750,7 +16750,7 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -16781,7 +16781,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-oauth2",
@@ -16805,7 +16805,7 @@ dependencies = [
[[package]]
name = "windmill-object-store"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-stream",
@@ -16839,7 +16839,7 @@ dependencies = [
[[package]]
name = "windmill-operator"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"futures",
@@ -16857,7 +16857,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -16866,7 +16866,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -16878,7 +16878,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"serde_json",
@@ -16890,7 +16890,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"gosyn",
@@ -16902,7 +16902,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -16914,7 +16914,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"serde_json",
@@ -16926,7 +16926,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"nu-parser",
@@ -16937,7 +16937,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16948,7 +16948,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16960,7 +16960,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -16971,7 +16971,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -16993,7 +16993,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -17007,7 +17007,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -17024,7 +17024,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -17037,7 +17037,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"serde",
@@ -17049,7 +17049,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"lazy_static",
@@ -17067,7 +17067,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -17083,7 +17083,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17099,7 +17099,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"serde",
@@ -17110,7 +17110,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -17147,7 +17147,7 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"const_format",
@@ -17185,7 +17185,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"getrandom 0.3.4",
"wasm-bindgen",
@@ -17196,7 +17196,7 @@ dependencies = [
[[package]]
name = "windmill-store"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-recursion",
@@ -17225,7 +17225,7 @@ dependencies = [
[[package]]
name = "windmill-test-utils"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17249,7 +17249,7 @@ dependencies = [
[[package]]
name = "windmill-trigger"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17282,7 +17282,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17302,7 +17302,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17336,7 +17336,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17371,7 +17371,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17394,7 +17394,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17418,7 +17418,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-nats",
@@ -17442,7 +17442,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17477,7 +17477,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17505,7 +17505,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-trait",
@@ -17528,7 +17528,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"bitflags 2.9.4",
@@ -17547,7 +17547,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"anyhow",
"async-once-cell",
@@ -17656,7 +17656,7 @@ dependencies = [
[[package]]
name = "windmill-worker-volumes"
version = "1.668.2"
version = "1.668.3"
dependencies = [
"bytes",
"futures",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.668.2"
version = "1.668.3"
authors.workspace = true
edition.workspace = true
@@ -82,7 +82,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal"]
[workspace.package]
version = "1.668.2"
version = "1.668.3"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -1,20 +0,0 @@
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
'test-workspace',
'test-user',
'
import inspect
import sys
def greet(name: str) -> str:
# Verify that __file__ is set on this module (same check typeguard does)
mod = sys.modules[__name__]
source_file = inspect.getfile(mod)
return f"Hello, {name}! from {source_file}"
def main():
return greet("World")
',
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
'',
'',
'f/system/typechecked_helper', 12349, 'python3', '');

View File

@@ -923,43 +923,3 @@ async def main(item: str, qty: int, email: str):
.await;
Ok(())
}
#[cfg(feature = "python")]
#[sqlx::test(fixtures("base", "typechecked_python"))]
async fn test_typechecked_decorator_python(db: Pool<Postgres>) -> anyhow::Result<()> {
initialize_tracing().await;
let server = ApiServer::start(db.clone()).await?;
let port = server.addr.port();
let content = r#"
from f.system.typechecked_helper import greet
def main():
return greet("World")
"#
.to_owned();
let job = JobPayload::Code(RawCode {
hash: None,
content,
path: Some("f/system/test_typechecked".to_string()),
language: ScriptLang::Python3,
lock: None,
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
.into(),
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
cache_ttl: None,
cache_ignore_s3_path: None,
dedicated_worker: None,
modules: None,
});
let result = run_job_in_new_worker_until_complete(&db, false, job, port)
.await
.json_result()
.unwrap();
let result_str = result.as_str().unwrap();
assert!(result_str.starts_with("Hello, World! from "), "unexpected result: {result_str}");
Ok(())
}

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.668.2
version: 1.668.3
title: Windmill API
contact:

View File

@@ -2,7 +2,6 @@ import sys
import os
from importlib.abc import MetaPathFinder, Loader
from importlib.machinery import ModuleSpec, SourceFileLoader
from importlib.util import spec_from_file_location
import time
class WindmillLoader(Loader):
@@ -34,7 +33,7 @@ class WindmillFinder(MetaPathFinder):
fullpath = folder + "/" + splitted[-1] + ".py"
if os.path.exists(fullpath):
return spec_from_file_location(name, fullpath)
return ModuleSpec(name, SourceFileLoader(name, fullpath))
import urllib.parse
@@ -63,7 +62,7 @@ class WindmillFinder(MetaPathFinder):
return ModuleSpec(name, WindmillLoader(name))
with open(fullpath, "w+") as f:
f.write(r)
return spec_from_file_location(name, fullpath)
return ModuleSpec(name, SourceFileLoader(name, fullpath))
except urllib.error.HTTPError as e:
duration = time.time() - req_start
if e.code != 404:

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.668.2";
export const VERSION = "v1.668.3";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -43,70 +43,66 @@ export async function pushWorkspaceDependencies(
_befObj: any,
newDependenciesContent: string,
): Promise<void> {
try {
const res = workspaceDependenciesPathToLanguageAndFilename(path);
if (!res) {
throw new Error(`Unknown workspace dependencies file format: ${path}`);
}
const { language, name } = res;
const displayName = name
? `named dependencies "${name}"`
: `workspace default dependencies`;
// Fetch remote workspace dependencies and compare content directly
try {
const remoteDeps = await wmill.getLatestWorkspaceDependencies({
workspace,
language,
name,
});
if (remoteDeps && remoteDeps.content === newDependenciesContent) {
log.info(
colors.green(
`${displayName} for ${language} are up-to-date, skipping push`,
),
);
return;
}
} catch (e: any) {
// If 404 or not found, the dependency doesn't exist remotely yet - proceed with push
if (e.status !== 404 && !e.message?.includes("not found")) {
throw e;
}
}
log.info(
colors.yellow(
`Pushing ${
name ? "named" : "workspace default"
} dependencies for ${language}...`,
),
const res = workspaceDependenciesPathToLanguageAndFilename(path);
if (!res) {
throw new Error(
`Unknown workspace dependencies file format: ${path}. ` +
`Valid files: package.json, requirements.in, composer.json, go.mod, modules.json`
);
}
await wmill.createWorkspaceDependencies({
const { language, name } = res;
const displayName = name
? `named dependencies "${name}"`
: `workspace default dependencies`;
// Fetch remote workspace dependencies and compare content directly
try {
const remoteDeps = await wmill.getLatestWorkspaceDependencies({
workspace,
requestBody: {
name,
content: newDependenciesContent,
language,
workspace_id: workspace,
// Description is not supported in cli, it will use old description
description: undefined,
},
language,
name,
});
log.info(
colors.green(`Successfully pushed ${displayName} for ${language}`),
);
} catch (error: any) {
log.error(
colors.red(`Failed to push workspace dependencies: ${error.message}`),
);
throw error;
if (remoteDeps && remoteDeps.content === newDependenciesContent) {
log.info(
colors.green(
`${displayName} for ${language} are up-to-date, skipping push`,
),
);
return;
}
} catch (e: any) {
// If 404 or not found, the dependency doesn't exist remotely yet - proceed with push
if (e.status !== 404 && !e.message?.includes("not found")) {
throw e;
}
}
log.info(
colors.yellow(
`Pushing ${
name ? "named" : "workspace default"
} dependencies for ${language}...`,
),
);
await wmill.createWorkspaceDependencies({
workspace,
requestBody: {
name,
content: newDependenciesContent,
language,
workspace_id: workspace,
// Description is not supported in cli, it will use old description
description: undefined,
},
});
log.info(
colors.green(`Successfully pushed ${displayName} for ${language}`),
);
}
export default command;

View File

@@ -154,18 +154,27 @@ export async function pushFlow(
const localFlow = (await yamlParseFile(localPath + "flow.yaml")) as FlowFile;
const fileReader = async (path: string) => await readFile(localPath + path, "utf-8");
const missingFiles: string[] = [];
await replaceInlineScripts(
localFlow.value.modules,
fileReader,
log,
localPath,
SEP
SEP,
undefined,
missingFiles
);
if (localFlow.value.failure_module) {
await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, localPath, SEP);
await replaceInlineScripts([localFlow.value.failure_module], fileReader, log, localPath, SEP, undefined, missingFiles);
}
if (localFlow.value.preprocessor_module) {
await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, localPath, SEP);
await replaceInlineScripts([localFlow.value.preprocessor_module], fileReader, log, localPath, SEP, undefined, missingFiles);
}
if (missingFiles.length > 0) {
log.warn(colors.yellow(
`Warning: missing inline script file(s): ${missingFiles.join(", ")}. ` +
`The flow will be pushed with unresolved !inline references.`
));
}
if (flow) {
@@ -272,11 +281,26 @@ async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
const modules = (f as any).value?.modules;
if (modules && Array.isArray(modules) && modules.length > 0) {
console.log(colors.bold("Steps:"));
for (const mod of modules) {
const type = mod.value?.type ?? "unknown";
const detail = mod.value?.language ?? mod.value?.path ?? "";
console.log(` ${mod.id}: ${type}${detail ? " (" + detail + ")" : ""}`);
function printModules(mods: any[], indent: string = " ") {
for (const mod of mods) {
const type = mod.value?.type ?? "unknown";
const detail = mod.value?.language ?? mod.value?.path ?? "";
console.log(`${indent}${mod.id}: ${type}${detail ? " (" + detail + ")" : ""}`);
if (type === "branchall" || type === "branchone") {
for (const branch of mod.value?.branches ?? []) {
console.log(`${indent} Branch: ${branch.summary || "(default)"}`);
if (branch.modules) printModules(branch.modules, indent + " ");
}
if (type === "branchone" && mod.value?.default) {
console.log(`${indent} Default:`);
printModules(mod.value.default, indent + " ");
}
} else if (type === "forloopflow" || type === "whileloopflow") {
if (mod.value?.modules) printModules(mod.value.modules, indent + " ");
}
}
}
printModules(modules);
}
}
}
@@ -493,7 +517,6 @@ async function preview(
process.exitCode = 1;
return;
}
log.error(`Flow preview failed: ${JSON.stringify(e.body)}`);
}
throw e;
}

View File

@@ -67,17 +67,20 @@ async function list(
let successFilter = opts.success;
if (opts.failed) successFilter = false;
const jobs = await wmill.listJobs({
const limit = Math.min(opts.limit ?? 30, 100);
const allJobs = await wmill.listJobs({
workspace: workspace.workspaceId,
scriptPathExact: opts.scriptPath,
createdBy: opts.createdBy,
running: opts.running,
success: successFilter,
perPage: Math.min(opts.limit ?? 30, 100),
perPage: limit,
jobKinds: opts.jobKinds ?? "script,flow,singlestepflow",
label: opts.label,
hasNullParent: opts.all ? undefined : true,
});
// API may return more than perPage — enforce limit client-side
const jobs = allJobs.slice(0, limit);
if (opts.json) {
console.log(JSON.stringify(jobs));

View File

@@ -625,7 +625,13 @@ export async function runLint(
throw new Error(`Path is not a directory: ${targetDirectory}`);
}
const ignore = await ignoreF(mergedOpts);
// When the user specifies a subdirectory (that doesn't contain wmill.yaml),
// skip include/exclude filters since they're relative to the project root.
const isSubdirectory = explicitTargetDirectory &&
!(await stat(path.join(targetDirectory, "wmill.yaml")).catch(() => null));
const ignore = isSubdirectory
? (_p: string, _isDir: boolean) => false
: await ignoreF(mergedOpts);
const root = await FSFSElement(targetDirectory, [], false);
const validator = new WindmillYamlValidator();
@@ -640,9 +646,10 @@ export async function runLint(
if (entry.isDirectory || entry.ignored) {
continue;
}
scannedFiles += 1;
const normalizedPath = normalizePath(entry.path);
scannedFiles += 1;
if (!YAML_FILE_REGEX.test(normalizedPath)) {
continue;
}
@@ -742,7 +749,11 @@ export function printReport(report: LintReport, jsonOutput: boolean) {
}
}
async function lint(opts: LintOptions, directory?: string) {
async function lint(opts: LintOptions & { watch?: boolean }, directory?: string) {
if (opts.watch) {
await lintWatch(opts, directory);
return;
}
try {
const report = await runLint(opts, directory);
printReport(report, !!opts.json);
@@ -770,6 +781,37 @@ async function lint(opts: LintOptions, directory?: string) {
}
}
async function lintWatch(opts: LintOptions, directory?: string) {
const { watch } = await import("node:fs");
const targetDir = directory ? path.resolve(process.cwd(), directory) : process.cwd();
log.info(colors.blue(`Watching ${targetDir} for changes... (Ctrl+C to stop)`));
async function runAndReport() {
try {
const report = await runLint(opts, directory);
// Clear screen for readability
process.stdout.write("\x1Bc");
log.info(colors.gray(`[${new Date().toLocaleTimeString()}] Lint results:\n`));
printReport(report, false);
} catch (error) {
log.error(error instanceof Error ? error.message : String(error));
}
}
await runAndReport();
let debounce: ReturnType<typeof setTimeout> | null = null;
watch(targetDir, { recursive: true }, (_event, filename) => {
if (!filename || !filename.toString().endsWith(".yaml") && !filename.toString().endsWith(".yml")) return;
if (debounce) clearTimeout(debounce);
debounce = setTimeout(runAndReport, 300);
});
// Keep the process alive
await new Promise(() => {});
}
const command = new Command()
.description(
"Validate Windmill flow, schedule, and trigger YAML files in a directory",
@@ -781,6 +823,7 @@ const command = new Command()
"--locks-required",
"Fail if scripts or flow inline scripts that need locks have no locks",
)
.option("-w, --watch", "Watch for file changes and re-lint automatically")
.action(lint as any);
export default command;

View File

@@ -126,20 +126,25 @@ async function push(opts: PushOptions, filePath: string) {
await requireLogin(opts);
// Warn if metadata appears stale (content changed since last generate-metadata)
// Warn about metadata state before pushing
try {
const content = await readFile(filePath, "utf-8");
const remotePath = removeExtensionToPath(filePath).replaceAll(SEP, "/");
const contentHash = await generateHash(content + remotePath);
const conf = await readLockfile();
if (!(await checkifMetadataUptodate(remotePath, contentHash, conf))) {
const hasLockEntry = conf.locks && (conf.locks[remotePath] !== undefined || conf.locks[`${remotePath}.ts`] !== undefined);
if (!hasLockEntry) {
log.warn(colors.yellow(
`No metadata generated yet for ${filePath}. Run 'wmill generate-metadata' to generate schema and lock.`
));
} else if (!(await checkifMetadataUptodate(remotePath, contentHash, conf))) {
log.warn(colors.yellow(
`Metadata for ${filePath} appears stale (content changed since last 'wmill generate-metadata').\n` +
`The schema and lock may not match the current code. Consider running 'wmill generate-metadata' first.`
));
}
} catch {
// Don't block push if staleness check fails
// Don't block push if check fails
}
const codebases = await listSyncCodebases(opts as SyncOptions);
@@ -1584,11 +1589,11 @@ async function history(
const command = new Command()
.description("script related commands")
.option("--show-archived", "Enable archived scripts in output")
.option("--show-archived", "Show archived scripts instead of active ones")
.option("--json", "Output as JSON (for piping to jq)")
.action(list as any)
.command("list", "list all scripts")
.option("--show-archived", "Enable archived scripts in output")
.option("--show-archived", "Show archived scripts instead of active ones")
.option("--json", "Output as JSON (for piping to jq)")
.action(list as any)
.command(

View File

@@ -76,7 +76,7 @@ import {
newRawAppPathAssigner,
PathAssigner,
} from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts";
import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts";
import { extractInlineScripts as extractInlineScriptsForFlows, extractCurrentMapping } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts";
import { generateFlowLockInternal } from "../flow/flow_metadata.ts";
import { isExecutionModeAnonymous } from "../app/app.ts";
import {
@@ -638,9 +638,16 @@ function ZipFSElement(
let inlineScripts;
try {
const assigner = newPathAssigner(defaultTs, { skipInlineScriptSuffix: getNonDottedPaths() });
inlineScripts = extractInlineScriptsForFlows(
// Preserve original !inline filenames from the flow to avoid phantom renames
const inlineMapping = extractCurrentMapping(
flow.value.modules as any,
{},
flow.value.failure_module,
flow.value.preprocessor_module,
);
inlineScripts = extractInlineScriptsForFlows(
flow.value.modules as any,
inlineMapping,
SEP,
defaultTs,
assigner,
@@ -649,7 +656,7 @@ function ZipFSElement(
if (flow.value.failure_module) {
inlineScripts.push(...extractInlineScriptsForFlows(
[flow.value.failure_module],
{},
inlineMapping,
SEP,
defaultTs,
assigner,
@@ -659,7 +666,7 @@ function ZipFSElement(
if (flow.value.preprocessor_module) {
inlineScripts.push(...extractInlineScriptsForFlows(
[flow.value.preprocessor_module],
{},
inlineMapping,
SEP,
defaultTs,
assigner,
@@ -1518,6 +1525,10 @@ async function compareDynFSElement(
continue;
}
if (k.startsWith("dependencies/")) {
if (!workspaceDependenciesPathToLanguageAndFilename(k)) {
log.warn(`Skipping unrecognized workspace dependencies file: ${k}`);
continue;
}
log.info(`Adding workspace dependencies file: ${k}`);
}
changes.push({ name: "added", path: k, content: v });

View File

@@ -378,6 +378,7 @@ const triggerTemplates: Record<TriggerType, Record<string, any>> = {
email: {
script_path: "",
is_flow: false,
local_part: "",
enabled: false,
},
};
@@ -409,6 +410,27 @@ async function newTrigger(opts: GlobalOptions & { kind: string }, path: string)
log.info(colors.green(`Created ${filePath}`));
}
const TRIGGER_SKIP_FIELDS = new Set(["workspace_id", "extra_perms", "edited_by", "edited_at"]);
function printTriggerDetails(trigger: any, kind: string) {
console.log(colors.bold("Path:") + " " + trigger.path);
console.log(colors.bold("Kind:") + " " + kind);
console.log(colors.bold("Enabled:") + " " + (trigger.enabled ?? trigger.mode ?? "-"));
console.log(colors.bold("Script Path:") + " " + (trigger.script_path ?? ""));
console.log(colors.bold("Is Flow:") + " " + (trigger.is_flow ? "true" : "false"));
// Show all other non-internal fields
for (const [key, value] of Object.entries(trigger)) {
if (["path", "enabled", "mode", "script_path", "is_flow"].includes(key)) continue;
if (TRIGGER_SKIP_FIELDS.has(key)) continue;
if (value === undefined || value === null || value === "") continue;
const display = Array.isArray(value) ? (value.length > 0 ? JSON.stringify(value) : "[]") :
typeof value === "object" ? JSON.stringify(value) : String(value);
if (display === "[]" || display === "{}") continue;
const label = key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());
console.log(colors.bold(label + ":") + " " + display);
}
}
async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path: string) {
if (opts.json) log.setSilent(true);
const workspace = await resolveWorkspace(opts);
@@ -422,11 +444,7 @@ async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path
if (opts.json) {
console.log(JSON.stringify(trigger));
} else {
console.log(colors.bold("Path:") + " " + (trigger as any).path);
console.log(colors.bold("Kind:") + " " + opts.kind);
console.log(colors.bold("Enabled:") + " " + ((trigger as any).enabled ?? "-"));
console.log(colors.bold("Script Path:") + " " + ((trigger as any).script_path ?? ""));
console.log(colors.bold("Is Flow:") + " " + ((trigger as any).is_flow ? "true" : "false"));
printTriggerDetails(trigger as any, opts.kind);
}
return;
}
@@ -451,11 +469,7 @@ async function get(opts: GlobalOptions & { json?: boolean; kind?: string }, path
if (opts.json) {
console.log(JSON.stringify(trigger));
} else {
console.log(colors.bold("Path:") + " " + trigger.path);
console.log(colors.bold("Kind:") + " " + kind);
console.log(colors.bold("Enabled:") + " " + (trigger.enabled ?? (trigger as any).mode ?? "-"));
console.log(colors.bold("Script Path:") + " " + (trigger.script_path ?? ""));
console.log(colors.bold("Is Flow:") + " " + (trigger.is_flow ? "true" : "false"));
printTriggerDetails(trigger, kind);
}
return;
}

View File

@@ -550,7 +550,7 @@ export async function resolveWorkspace(
}
// If everything failed, show error
log.info(colors.red.bold("No workspace given and no default set."));
log.info(colors.red.bold("No workspace given and no default set. Run 'wmill workspace add' to configure one."));
return process.exit(-1);
}

View File

@@ -5260,6 +5260,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory
- \`--json\` - Output results in JSON format
- \`--fail-on-warn\` - Exit with code 1 when warnings are emitted
- \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks
- \`-w, --watch\` - Watch for file changes and re-lint automatically
### queues
@@ -5328,13 +5329,13 @@ schedule related commands
script related commands
**Options:**
- \`--show-archived\` - Enable archived scripts in output
- \`--show-archived\` - Show archived scripts instead of active ones
- \`--json\` - Output as JSON (for piping to jq)
**Subcommands:**
- \`script list\` - list all scripts
- \`--show-archived\` - Enable archived scripts in output
- \`--show-archived\` - Show archived scripts instead of active ones
- \`--json\` - Output as JSON (for piping to jq)
- \`script push <path:file>\` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
- \`--message <message:string>\` - Deployment message

View File

@@ -78,7 +78,7 @@ export {
token,
};
export const VERSION = "1.668.2";
export const VERSION = "1.668.3";
// Re-exported from constants.ts to maintain backwards compatibility
export { WM_FORK_PREFIX } from "./core/constants.ts";
@@ -231,7 +231,9 @@ async function main() {
} catch (e) {
if (e && typeof e === "object" && "name" in e && e.name === "ApiError") {
const body = (e as any).body;
const bodyStr = typeof body === "object" && body !== null ? JSON.stringify(body) : body;
let bodyStr = typeof body === "object" && body !== null ? JSON.stringify(body) : String(body ?? "");
// Strip backend source file references like (flows.rs:1400) or @scripts.rs:123:45
bodyStr = bodyStr.replace(/\s*[@(]\w+\.rs:\d+[:\d]*\)?/g, "");
log.error(
"Server failed. " + (e as any).statusText + ": " + bodyStr
);

View File

@@ -107,6 +107,7 @@ export function getHeaders(): Record<string, string> | undefined {
export async function digestDir(path: string, conf: string) {
const hashes: string = [];
const entries = await readdir(path, { withFileTypes: true });
entries.sort((a, b) => a.name.localeCompare(b.name));
for (const e of entries) {
const npath = path + "/" + e.name;
if (e.isFile()) {

View File

@@ -13,7 +13,8 @@ async function replaceRawscriptInline(
fileReader: (path: string) => Promise<string>,
logger: { info: (message: string) => void; error: (message: string) => void },
separator: string,
removeLocks?: string[]
removeLocks?: string[],
missingFiles?: string[]
): Promise<void> {
if (!rawscript.content || !rawscript.content.startsWith("!inline")) {
return;
@@ -31,6 +32,7 @@ async function replaceRawscriptInline(
rawscript.content = await fileReader(newPath);
} catch {
logger.error(`Script file ${newPath} not found`);
if (missingFiles) missingFiles.push(path);
}
}
@@ -76,14 +78,14 @@ export async function replaceInlineScripts(
localPath: string,
separator: string = "/",
removeLocks?: string[],
// renamer?: (path: string, newPath: string) => void,
// deleter?: (path: string) => void
): Promise<void> {
missingFiles?: string[],
): Promise<string[]> {
const missing = missingFiles ?? [];
await Promise.all(modules.map(async (module) => {
if (!module.value) {
throw new Error(`Module value is undefined for module ${module.id}`);
}
if (module.value.type === "rawscript") {
await replaceRawscriptInline(
module.id,
@@ -91,19 +93,20 @@ export async function replaceInlineScripts(
fileReader,
logger,
separator,
removeLocks
removeLocks,
missing
);
} else if (module.value.type === "forloopflow" || module.value.type === "whileloopflow") {
await replaceInlineScripts(module.value.modules, fileReader, logger, localPath, separator, removeLocks);
await replaceInlineScripts(module.value.modules, fileReader, logger, localPath, separator, removeLocks, missing);
} else if (module.value.type === "branchall") {
await Promise.all(module.value.branches.map(async (branch) => {
await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks);
await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks, missing);
}));
} else if (module.value.type === "branchone") {
await Promise.all(module.value.branches.map(async (branch) => {
await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks);
await replaceInlineScripts(branch.modules, fileReader, logger, localPath, separator, removeLocks, missing);
}));
await replaceInlineScripts(module.value.default, fileReader, logger, localPath, separator, removeLocks);
await replaceInlineScripts(module.value.default, fileReader, logger, localPath, separator, removeLocks, missing);
} else if (module.value.type === "aiagent") {
await Promise.all((module.value.tools ?? []).map(async (tool) => {
const toolValue = tool.value;
@@ -120,11 +123,13 @@ export async function replaceInlineScripts(
fileReader,
logger,
separator,
removeLocks
removeLocks,
missing
);
}));
}
}));
return missing;
}
/**

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.668.2",
"version": "1.668.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.668.2",
"version": "1.668.3",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.668.2",
"version": "1.668.3",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.668.2"
wmill = ">=1.668.3"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: '3.0.3'
info:
version: 1.668.2
version: 1.668.3
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.668.2'
ModuleVersion = '1.668.3'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.668.2"
version = "1.668.3"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -302,6 +302,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory
- `--json` - Output results in JSON format
- `--fail-on-warn` - Exit with code 1 when warnings are emitted
- `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks
- `-w, --watch` - Watch for file changes and re-lint automatically
### queues
@@ -370,13 +371,13 @@ schedule related commands
script related commands
**Options:**
- `--show-archived` - Enable archived scripts in output
- `--show-archived` - Show archived scripts instead of active ones
- `--json` - Output as JSON (for piping to jq)
**Subcommands:**
- `script list` - list all scripts
- `--show-archived` - Enable archived scripts in output
- `--show-archived` - Show archived scripts instead of active ones
- `--json` - Output as JSON (for piping to jq)
- `script push <path:file>` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
- `--message <message:string>` - Deployment message

View File

@@ -1829,6 +1829,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory
- \`--json\` - Output results in JSON format
- \`--fail-on-warn\` - Exit with code 1 when warnings are emitted
- \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks
- \`-w, --watch\` - Watch for file changes and re-lint automatically
### queues
@@ -1897,13 +1898,13 @@ schedule related commands
script related commands
**Options:**
- \`--show-archived\` - Enable archived scripts in output
- \`--show-archived\` - Show archived scripts instead of active ones
- \`--json\` - Output as JSON (for piping to jq)
**Subcommands:**
- \`script list\` - list all scripts
- \`--show-archived\` - Enable archived scripts in output
- \`--show-archived\` - Show archived scripts instead of active ones
- \`--json\` - Output as JSON (for piping to jq)
- \`script push <path:file>\` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
- \`--message <message:string>\` - Deployment message

View File

@@ -307,6 +307,7 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory
- `--json` - Output results in JSON format
- `--fail-on-warn` - Exit with code 1 when warnings are emitted
- `--locks-required` - Fail if scripts or flow inline scripts that need locks have no locks
- `-w, --watch` - Watch for file changes and re-lint automatically
### queues
@@ -375,13 +376,13 @@ schedule related commands
script related commands
**Options:**
- `--show-archived` - Enable archived scripts in output
- `--show-archived` - Show archived scripts instead of active ones
- `--json` - Output as JSON (for piping to jq)
**Subcommands:**
- `script list` - list all scripts
- `--show-archived` - Enable archived scripts in output
- `--show-archived` - Show archived scripts instead of active ones
- `--json` - Output as JSON (for piping to jq)
- `script push <path:file>` - push a local script spec. This overrides any remote versions. Use the script file (.ts, .js, .py, .sh
- `--message <message:string>` - Deployment message

View File

@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.668.2",
"version": "1.668.3",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"]

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.668.2",
"version": "1.668.3",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"sideEffects": false,

View File

@@ -1 +1 @@
1.668.2
1.668.3