mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-12 08:02:44 +00:00
Merge remote-tracking branch 'origin/main' into python-instance-baseline
# Conflicts: # frontend/src/lib/components/instanceSettings.ts
This commit is contained in:
@@ -28,7 +28,7 @@ ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV GO_PATH=/usr/local/go/bin/go
|
||||
|
||||
# UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.11.24/uv-installer.sh | sh && mv /usr/local/cargo/bin/uv /usr/local/bin/uv
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.13.0/uv-installer.sh | sh && mv /usr/local/cargo/bin/uv /usr/local/bin/uv
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.11.24"
|
||||
version: "0.13.0"
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
node-version: "24"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "0.11.24"
|
||||
version: "0.13.0"
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "1.829.0"
|
||||
".": "1.830.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## [1.830.0](https://github.com/windmill-labs/windmill/compare/v1.829.0...v1.830.0) (2026-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a jobs:run:dependencies token scope for lock generation ([#11643](https://github.com/windmill-labs/windmill/issues/11643)) ([1dc4ed5](https://github.com/windmill-labs/windmill/commit/1dc4ed5e7fb86c9658f1f877e7b92cd9dab5e283))
|
||||
* let operators compose full-code apps when the workspace grants it ([#11229](https://github.com/windmill-labs/windmill/issues/11229)) ([02e4697](https://github.com/windmill-labs/windmill/commit/02e4697157112581e9e2a9303180077126db714b))
|
||||
* operators get AI sessions ([#11443](https://github.com/windmill-labs/windmill/issues/11443)) ([338c4e9](https://github.com/windmill-labs/windmill/commit/338c4e921dc3185d886c8b040f253285adf20efb))
|
||||
* replace the hub_sync script with an opt-in daily server sync ([#11624](https://github.com/windmill-labs/windmill/issues/11624)) ([2b55a8a](https://github.com/windmill-labs/windmill/commit/2b55a8afbc8a86c6cebe97b1cab02c313b986f22))
|
||||
* retry transient AI provider failures before output reaches readers ([#11623](https://github.com/windmill-labs/windmill/issues/11623)) ([d3ddef0](https://github.com/windmill-labs/windmill/commit/d3ddef0b7ec4705aa578c4eca24b96e9152485bd))
|
||||
* stop offering deno for new scripts, ship it only on full images ([#11648](https://github.com/windmill-labs/windmill/issues/11648)) ([cda7883](https://github.com/windmill-labs/windmill/commit/cda78830fde1b96bd3de2674e23c245b50e3c051))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** count superadmins as able to preserve ownership on push ([#11653](https://github.com/windmill-labs/windmill/issues/11653)) ([70db567](https://github.com/windmill-labs/windmill/commit/70db567a78b1e1bc9e2dcc9ff2139951d778117e))
|
||||
* harden app secret validation ([#11655](https://github.com/windmill-labs/windmill/issues/11655)) ([8556a7b](https://github.com/windmill-labs/windmill/commit/8556a7bebe093e0bd61c33d67415fd553f0070ad))
|
||||
* open the chat + menu without waiting on the skills and mcp fetches ([#11645](https://github.com/windmill-labs/windmill/issues/11645)) ([1fea1aa](https://github.com/windmill-labs/windmill/commit/1fea1aa6609d644e02bac8d16186f9a2dfcb63cd))
|
||||
* say Running on a chat run card only once its job exists ([#11627](https://github.com/windmill-labs/windmill/issues/11627)) ([d046187](https://github.com/windmill-labs/windmill/commit/d046187e3edd01535240c6080b483afe154ae189))
|
||||
|
||||
## [1.829.0](https://github.com/windmill-labs/windmill/compare/v1.828.0...v1.829.0) (2026-10-09)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -241,7 +241,7 @@ ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV GO_PATH=/usr/local/go/bin/go
|
||||
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.11.24/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.13.0/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
# Preinstall python runtimes to temp build location (will copy with world-writable perms later)
|
||||
# --compile-bytecode precompiles the stdlib to .pyc so jobs don't recompile it on every run
|
||||
|
||||
+1
-1
@@ -214,7 +214,7 @@ Whole classes of defect compile and unit-test clean:
|
||||
- **Stack overflow from a large buffer in an async block.** An array declared across an
|
||||
`.await` is baked into the future's state; once that future is boxed a few layers deep
|
||||
by the job poller, two 16 KB arrays abort the worker *process* (`thread
|
||||
'tokio-runtime-worker' has overflowed its stack`). Heap-allocate read buffers
|
||||
'tokio-rt-worker' has overflowed its stack`). Heap-allocate read buffers
|
||||
(`vec![0u8; N]`, not `[0u8; N]`).
|
||||
- Deadlocks from draining only one of a child's pipes, missed cancellation or timeout
|
||||
propagation, and anything depending on the real engine's output format.
|
||||
|
||||
Generated
+1228
-651
File diff suppressed because it is too large
Load Diff
+26
-30
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -88,7 +88,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -451,7 +451,7 @@ headers = "^0"
|
||||
hyper = { version = "^1", features = ["full"] }
|
||||
hyper-tls = "^0.6"
|
||||
hyper-util = { version = "^0.1", features = ["client-legacy", "http1", "tokio"] }
|
||||
tokio = { version = "=1.46.1", features = ["full", "tracing", "time"] }
|
||||
tokio = { version = "=1.53.2", features = ["full", "tracing", "time"] }
|
||||
tokio-stream = { version = "0.1.17" }
|
||||
tower = "^0"
|
||||
tower-http = { version = "^0.6", features = ["trace", "cors", "catch-panic"] }
|
||||
@@ -519,33 +519,29 @@ aws-sdk-rds = "^1"
|
||||
async-trait = "0.1.88"
|
||||
|
||||
|
||||
v8 = "=137.1.0" # Exact version NOTE: Do not forget to update version and hash in flake.nix
|
||||
# deno_* pin set: deno v2.4.0 base, with deno_ast force-overridden to =0.51.0.
|
||||
# Rationale: deno_ast 0.51.0 is the first version pulling swc_common =14.0.4,
|
||||
# the first swc_common patch that dropped `pub use serde::__private as serde;`
|
||||
# (the line that capped our workspace serde pin at =1.0.220). v2.4.0's other
|
||||
# pins keep deno_tls at 0.196.0 which uses permissive `rustls ^0.23.11`,
|
||||
# compatible with aws-sdk-bedrockruntime's `^0.23.31` requirement. deno_tls
|
||||
# 0.198+ tightened that to exact `=0.23.28`, which would have made any
|
||||
# meaningful deno bump resolver-impossible against aws-sdk.
|
||||
deno_fetch = "0.233.0"
|
||||
deno_tls = "0.196.0"
|
||||
deno_console = "0.209.0"
|
||||
deno_url = "0.209.0"
|
||||
deno_webidl = "0.209.0"
|
||||
deno_web = "0.240.0"
|
||||
# Sibling release of the pinned deno_core 0.352 / deno_web 0.240 stack
|
||||
# (its deps are deno_core ^0.352, deno_web ^0.240, deno_error =0.6.1). A newer
|
||||
# deno_crypto would force bumping the whole deno stack.
|
||||
deno_crypto = "0.223.0"
|
||||
deno_io = "0.119.0"
|
||||
deno_fs = "0.119.0"
|
||||
deno_net = "0.201.0"
|
||||
deno_core = "0.352.0"
|
||||
# deno_* pin set: the crates deno published together on 2026-09-16 (deno_core
|
||||
# 0.412).
|
||||
# V8 is pinned exactly because flake.nix fetches the prebuilt archive of that
|
||||
# version: update its version and hash there with any bump. No default features,
|
||||
# so the pin leaves the feature set deno_core picks untouched.
|
||||
v8 = { version = "=150.4.0", default-features = false }
|
||||
# These release as a matched set: each pins its siblings, deno_error exactly,
|
||||
# and deno_tls pins rustls exactly (=0.23.40, the workspace `rustls` pin below
|
||||
# must equal it). A bump moves all of them at once.
|
||||
# deno_ast is independent of that set and stays at 0.51.0: a newer one pulls
|
||||
# swc_common 17, which would force bumping every swc_* pin the parsers use.
|
||||
deno_fetch = "0.283.0"
|
||||
deno_tls = "0.246.0"
|
||||
deno_webidl = "0.259.0"
|
||||
deno_web = "0.290.0"
|
||||
deno_crypto = "0.273.0"
|
||||
deno_net = "0.251.0"
|
||||
deno_core = "0.412.0"
|
||||
deno_ast = { version = "=0.51.0", features = ["transpiling"] }
|
||||
deno_permissions = "0.68.0"
|
||||
deno_telemetry = "0.31.0"
|
||||
deno_error = "=0.6.1"
|
||||
deno_permissions = "0.118.0"
|
||||
deno_telemetry = "0.81.0"
|
||||
deno_error = "=0.7.1"
|
||||
sys_traits = { version = "0.1.28", features = ["real", "libc", "winapi"] }
|
||||
rustls-pemfile = "2.2.0"
|
||||
|
||||
# only used with special deno_core_mac feature to prevent ffi issue on macos, requires libffi to be installed
|
||||
@@ -682,7 +678,7 @@ aws-sdk-sqs = "^1.89.0"
|
||||
aws-sdk-sts = "^1.91.0"
|
||||
aws-sdk-sso = "=1.77.0"
|
||||
aws-sdk-ssooidc = "=1.78.0"
|
||||
rustls = "=0.23.35"
|
||||
rustls = "=0.23.40"
|
||||
async-once-cell = "0.5.4"
|
||||
size = "0.5.0"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
5d7726c4979070ee8db4fb9e8014152872a641ba
|
||||
599eae0034b34c5e0d3d42df37cf18bbe01f9b49
|
||||
|
||||
@@ -351,13 +351,16 @@ async fn parse_python_imports_inner(
|
||||
temp_script_refs: &Option<HashMap<String, String>>,
|
||||
) -> error::Result<HashMap<String, NImportResolved>> {
|
||||
tracing::debug!("Parsing python imports for path: {}", path);
|
||||
let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code);
|
||||
let PythonAnnotations { py310, py311, py312, py313, py314, py315, .. } =
|
||||
PythonAnnotations::parse(&code);
|
||||
tracing::debug!(
|
||||
"Found python annotations - py310: {}, py311: {}, py312: {}, py313: {}",
|
||||
"Found python annotations - py310: {}, py311: {}, py312: {}, py313: {}, py314: {}, py315: {}",
|
||||
py310,
|
||||
py311,
|
||||
py312,
|
||||
py313
|
||||
py313,
|
||||
py314,
|
||||
py315
|
||||
);
|
||||
|
||||
let mut push_version_specifiers = |perform, unparsed: String| -> error::Result<()> {
|
||||
@@ -373,6 +376,8 @@ async fn parse_python_imports_inner(
|
||||
push_version_specifiers(py311, "==3.11.*".to_owned())?;
|
||||
push_version_specifiers(py312, "==3.12.*".to_owned())?;
|
||||
push_version_specifiers(py313, "==3.13.*".to_owned())?;
|
||||
push_version_specifiers(py314, "==3.14.*".to_owned())?;
|
||||
push_version_specifiers(py315, "==3.15.*".to_owned())?;
|
||||
|
||||
for x in code.lines() {
|
||||
if x.starts_with("# py:") || x.starts_with("#py:") {
|
||||
|
||||
+24
-24
@@ -6216,7 +6216,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -6302,7 +6302,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6314,7 +6314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"serde",
|
||||
@@ -6323,7 +6323,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6335,7 +6335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6347,7 +6347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -6359,7 +6359,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6371,7 +6371,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6383,7 +6383,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -6394,7 +6394,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6405,7 +6405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -6417,7 +6417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-asset"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6428,7 +6428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -6450,7 +6450,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-r"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -6462,7 +6462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6476,7 +6476,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case",
|
||||
@@ -6493,7 +6493,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6506,7 +6506,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql-asset"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -6518,7 +6518,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6536,7 +6536,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts-asset"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde-wasm-bindgen",
|
||||
@@ -6552,7 +6552,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wac"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rustpython-ast",
|
||||
@@ -6568,7 +6568,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-wasm"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"getrandom 0.2.17",
|
||||
@@ -6600,7 +6600,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -6614,7 +6614,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
|
||||
@@ -12,7 +12,7 @@ resolver = "2"
|
||||
members = ["."]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
edition = "2021"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
|
||||
|
||||
+4
-1
@@ -124,7 +124,8 @@ use windmill_worker::{
|
||||
BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS,
|
||||
DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, JAVA_CACHE_DIR, NU_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, PY312_CACHE_DIR, PY313_CACHE_DIR,
|
||||
RUBY_CACHE_DIR, RUST_CACHE_DIR, R_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR,
|
||||
PY314_CACHE_DIR, PY315_CACHE_DIR, RUBY_CACHE_DIR, RUST_CACHE_DIR, R_CACHE_DIR,
|
||||
TAR_JAVA_CACHE_DIR, UV_CACHE_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -2183,6 +2184,8 @@ pub async fn run_workers(
|
||||
&*PY311_CACHE_DIR,
|
||||
&*PY312_CACHE_DIR,
|
||||
&*PY313_CACHE_DIR,
|
||||
&*PY314_CACHE_DIR,
|
||||
&*PY315_CACHE_DIR,
|
||||
&*BUN_BUNDLE_CACHE_DIR,
|
||||
&*GO_CACHE_DIR,
|
||||
&*GO_BIN_CACHE_DIR,
|
||||
|
||||
@@ -5000,6 +5000,101 @@ async fn test_flow_lock_all(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A deployed flow runs a multi-step loop body as the flow node its deploy stored, which
|
||||
/// carries its own copy of the flow's `preserve_step_tags`. A preview never loads that node.
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_saved_flow_preserves_nested_step_tags(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
use futures::StreamExt;
|
||||
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
let client = reqwest::Client::new();
|
||||
let flow = json!({
|
||||
"path": "g/all/saved_step_tags", "summary": "", "description": "", "tag": "python3",
|
||||
"schema": { "type": "object", "properties": {}, "required": [] },
|
||||
"value": {
|
||||
"preserve_step_tags": true,
|
||||
"modules": [{
|
||||
"id": "loop",
|
||||
"value": {
|
||||
"type": "forloopflow", "skip_failures": false,
|
||||
"iterator": { "type": "static", "value": [2, 3] },
|
||||
"modules": [{
|
||||
"id": "own_tag",
|
||||
"value": {
|
||||
"type": "rawscript", "language": "bun", "lock": "", "tag": "bun",
|
||||
"content": "export function main(n: number) { return n * n; }",
|
||||
"input_transforms": {
|
||||
"n": { "type": "javascript", "expr": "flow_input.iter.value" }
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"id": "untagged",
|
||||
"value": {
|
||||
"type": "rawscript", "language": "bun", "lock": "",
|
||||
"content": "export function main(n: number) { return n + 1; }",
|
||||
"input_transforms": {
|
||||
"n": { "type": "javascript", "expr": "results.own_tag" }
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
let mut completed = listen_for_completed_jobs(&db).await;
|
||||
client
|
||||
.post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/flows/create"
|
||||
))
|
||||
.bearer_auth("SECRET_TOKEN")
|
||||
.json(&flow)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
let deployment = in_test_worker(&db, completed.next(), port).await.unwrap();
|
||||
let deployed = completed_job(deployment, &db).await;
|
||||
assert!(deployed.success, "{:?}", deployed.result);
|
||||
|
||||
let job_id = client
|
||||
.post(format!(
|
||||
"http://localhost:{port}/api/w/test-workspace/jobs/run/f/g/all/saved_step_tags"
|
||||
))
|
||||
.bearer_auth("SECRET_TOKEN")
|
||||
.json(&json!({}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.text()
|
||||
.await?
|
||||
.parse::<Uuid>()?;
|
||||
in_test_worker(&db, completed.find(&job_id), port).await;
|
||||
let job = completed_job(job_id, &db).await;
|
||||
assert!(job.success, "{:?}", job.result);
|
||||
let tags = sqlx::query_as::<_, (String, String)>(
|
||||
"WITH RECURSIVE descendants AS (
|
||||
SELECT id FROM v2_job WHERE id = $1
|
||||
UNION ALL
|
||||
SELECT child.id FROM v2_job child JOIN descendants ON child.parent_job = descendants.id
|
||||
)
|
||||
SELECT DISTINCT flow_step_id, tag FROM v2_job
|
||||
WHERE id IN (SELECT id FROM descendants) AND flow_step_id IN ('own_tag', 'untagged')
|
||||
ORDER BY flow_step_id, tag",
|
||||
)
|
||||
.bind(job_id)
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
tags,
|
||||
vec![
|
||||
("own_tag".to_owned(), "bun".to_owned()),
|
||||
("untagged".to_owned(), "python3".to_owned())
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.829.0
|
||||
version: 1.830.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -2109,18 +2109,23 @@ async fn get_id_from_secret(
|
||||
prefix: Option<&str>,
|
||||
) -> Result<i64> {
|
||||
let mc = build_crypt(db, w_id).await?;
|
||||
let decrypted = mc
|
||||
.decrypt_bytes_to_bytes(&(hex::decode(secret)?))
|
||||
.map_err(|e| Error::internal_err(e.to_string()))?;
|
||||
let mut bytes = str::from_utf8(&decrypted).map_err(to_anyhow)?;
|
||||
if let Some(prefix) = prefix {
|
||||
if !bytes.starts_with(prefix) {
|
||||
return Err(Error::BadRequest("Invalid secret".to_string()));
|
||||
}
|
||||
bytes = bytes.strip_prefix(prefix).unwrap_or("");
|
||||
id_from_secret(&mc, &secret, prefix)
|
||||
.ok_or_else(|| Error::BadRequest("Invalid secret".to_string()))
|
||||
}
|
||||
|
||||
/// The secret comes from the URL of unauthenticated routes, so every way it can be invalid
|
||||
/// must produce the same response: a caller must never learn why a secret was rejected.
|
||||
fn id_from_secret(
|
||||
mc: &magic_crypt::MagicCrypt256,
|
||||
secret: &str,
|
||||
prefix: Option<&str>,
|
||||
) -> Option<i64> {
|
||||
let decrypted = mc.decrypt_bytes_to_bytes(&hex::decode(secret).ok()?).ok()?;
|
||||
let plaintext = String::from_utf8(decrypted).ok()?;
|
||||
match prefix {
|
||||
Some(prefix) => plaintext.strip_prefix(prefix)?.parse().ok(),
|
||||
None => plaintext.parse().ok(),
|
||||
}
|
||||
let id: i64 = bytes.parse().map_err(to_anyhow)?;
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn get_public_resource(
|
||||
@@ -6834,6 +6839,39 @@ mod policy_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod app_secret_tests {
|
||||
use super::{id_from_secret, BUNDLE_SECRET_PREFIX};
|
||||
use magic_crypt::MagicCryptTrait;
|
||||
|
||||
#[test]
|
||||
fn every_invalid_secret_is_rejected_alike() {
|
||||
let mc = magic_crypt::new_magic_crypt!("workspace-key", 256);
|
||||
let other = magic_crypt::new_magic_crypt!("another-key", 256);
|
||||
let valid = hex::encode(mc.encrypt_str_to_bytes("42"));
|
||||
assert_eq!(id_from_secret(&mc, &valid, None), Some(42));
|
||||
let bundle = hex::encode(mc.encrypt_str_to_bytes(format!("{BUNDLE_SECRET_PREFIX}7")));
|
||||
assert_eq!(
|
||||
id_from_secret(&mc, &bundle, Some(BUNDLE_SECRET_PREFIX)),
|
||||
Some(7)
|
||||
);
|
||||
|
||||
let mut tampered = mc.encrypt_str_to_bytes("42");
|
||||
*tampered.last_mut().unwrap() ^= 1;
|
||||
for bad in [
|
||||
"not hex".to_string(),
|
||||
hex::encode(&tampered),
|
||||
hex::encode(other.encrypt_str_to_bytes("42")),
|
||||
hex::encode(mc.encrypt_bytes_to_bytes(&[0xff, 0xfe])),
|
||||
hex::encode(mc.encrypt_str_to_bytes("not a number")),
|
||||
valid.clone(),
|
||||
] {
|
||||
let prefix = (bad == valid).then_some(BUNDLE_SECRET_PREFIX);
|
||||
assert_eq!(id_from_secret(&mc, &bad, prefix), None, "{bad}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod raw_app_file_key_tests {
|
||||
use super::validate_raw_app_path_keys;
|
||||
|
||||
@@ -1201,6 +1201,8 @@ pub struct PythonAnnotations {
|
||||
pub py311: bool,
|
||||
pub py312: bool,
|
||||
pub py313: bool,
|
||||
pub py314: bool,
|
||||
pub py315: bool,
|
||||
pub sandbox: bool,
|
||||
}
|
||||
|
||||
@@ -2894,6 +2896,8 @@ pub enum PyVAlias {
|
||||
#[default]
|
||||
Py312,
|
||||
Py313,
|
||||
Py314,
|
||||
Py315,
|
||||
}
|
||||
|
||||
impl Into<pep440_rs::Version> for PyVAlias {
|
||||
@@ -2916,7 +2920,14 @@ impl PyVAlias {
|
||||
|
||||
pub fn all<T: From<PyVAlias>>() -> Vec<T> {
|
||||
use PyVAlias::*;
|
||||
vec![Py310.into(), Py311.into(), Py312.into(), Py313.into()]
|
||||
vec![
|
||||
Py310.into(),
|
||||
Py311.into(),
|
||||
Py312.into(),
|
||||
Py313.into(),
|
||||
Py314.into(),
|
||||
Py315.into(),
|
||||
]
|
||||
}
|
||||
/// `MAJOR.MINOR`, the form the instance settings store.
|
||||
pub fn version_string(self) -> String {
|
||||
@@ -2927,7 +2938,7 @@ impl PyVAlias {
|
||||
fn major(&self) -> u32 {
|
||||
use PyVAlias::*;
|
||||
match self {
|
||||
Py310 | Py311 | Py312 | Py313 => 3,
|
||||
Py310 | Py311 | Py312 | Py313 | Py314 | Py315 => 3,
|
||||
// Py400 | Py401 => 4
|
||||
}
|
||||
}
|
||||
@@ -2942,6 +2953,8 @@ impl PyVAlias {
|
||||
"311" => Some(Py311),
|
||||
"312" => Some(Py312),
|
||||
"313" => Some(Py313),
|
||||
"314" => Some(Py314),
|
||||
"315" => Some(Py315),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,16 +22,15 @@ deno_webidl.workspace = true
|
||||
deno_web.workspace = true
|
||||
deno_crypto.workspace = true
|
||||
deno_net.workspace = true
|
||||
deno_console.workspace = true
|
||||
deno_url.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_ast.workspace = true
|
||||
deno_tls.workspace = true
|
||||
deno_permissions.workspace = true
|
||||
deno_io.workspace = true
|
||||
deno_fs.workspace = true
|
||||
deno_telemetry.workspace = true
|
||||
deno_error.workspace = true
|
||||
sys_traits.workspace = true
|
||||
# Not used directly: depending on it is what makes the workspace pin bind.
|
||||
v8.workspace = true
|
||||
winapi.workspace = true
|
||||
|
||||
itertools.workspace = true
|
||||
@@ -58,14 +57,8 @@ deno_webidl.workspace = true
|
||||
deno_web.workspace = true
|
||||
deno_crypto.workspace = true
|
||||
deno_net.workspace = true
|
||||
deno_console.workspace = true
|
||||
deno_url.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_ast.workspace = true
|
||||
deno_tls.workspace = true
|
||||
deno_permissions.workspace = true
|
||||
deno_io.workspace = true
|
||||
deno_fs.workspace = true
|
||||
deno_telemetry.workspace = true
|
||||
deno_error.workspace = true
|
||||
winapi.workspace = true
|
||||
|
||||
@@ -1,107 +1,11 @@
|
||||
use deno_ast::{MediaType, ParseParams};
|
||||
use deno_core::{ModuleCodeString, ModuleName, SourceMapData};
|
||||
use deno_core::{Extension, ModuleCodeString, ModuleName, SourceMapData};
|
||||
use deno_error::JsErrorBox;
|
||||
use deno_fetch::FetchPermissions;
|
||||
use deno_net::NetPermissions;
|
||||
use deno_web::{BlobStore, TimersPermission};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fmt::Write as _;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PermissionsContainer;
|
||||
|
||||
impl FetchPermissions for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn check_net_url(
|
||||
&mut self,
|
||||
_url: &deno_core::url::Url,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_read<'a>(
|
||||
&mut self,
|
||||
_path: Cow<'a, Path>,
|
||||
_api_name: &str,
|
||||
_get_path: &'a dyn deno_fs::GetPath,
|
||||
) -> Result<deno_fs::CheckedPath<'a>, deno_io::fs::FsError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_write<'a>(
|
||||
&mut self,
|
||||
_path: Cow<'a, Path>,
|
||||
_api_name: &str,
|
||||
_get_path: &'a dyn deno_fs::GetPath,
|
||||
) -> Result<deno_fs::CheckedPath<'a>, deno_io::fs::FsError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_net_vsock(
|
||||
&mut self,
|
||||
_cid: u32,
|
||||
_port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
}
|
||||
|
||||
impl TimersPermission for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn allow_hrtime(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl NetPermissions for PermissionsContainer {
|
||||
fn check_read(
|
||||
&mut self,
|
||||
_p: &str,
|
||||
_api_name: &str,
|
||||
) -> Result<PathBuf, deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
fn check_write(
|
||||
&mut self,
|
||||
_p: &str,
|
||||
_api_name: &str,
|
||||
) -> Result<PathBuf, deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
fn check_net<T: AsRef<str>>(
|
||||
&mut self,
|
||||
_host: &(T, Option<u16>),
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
fn check_write_path<'a>(
|
||||
&mut self,
|
||||
_p: Cow<'a, Path>,
|
||||
_api_name: &str,
|
||||
) -> Result<Cow<'a, Path>, deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
_cid: u32,
|
||||
_port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
unreachable!("snapshotting")
|
||||
}
|
||||
}
|
||||
|
||||
deno_core::extension!(
|
||||
fetch,
|
||||
@@ -109,38 +13,29 @@ deno_core::extension!(
|
||||
esm = ["src/runtime.js"],
|
||||
);
|
||||
|
||||
// `extension_transpiler` callback for `deno_core::snapshot::create_snapshot`.
|
||||
// `extension_transpiler` callback for `deno_core::snapshot::create_snapshot`,
|
||||
// also applied to the residual lazy sources below.
|
||||
//
|
||||
// Specialized to our snapshot's inputs. Of the eight deno_* extensions
|
||||
// we register via `init()`, seven ship pre-built `.js` files
|
||||
// in their `esm` lists (webidl/url/console/web/crypto/fetch/net) — only
|
||||
// `deno_telemetry`'s `extension!` macro lists `.ts` files
|
||||
// (`telemetry.ts`, `util.ts`), so the TypeScript branch is needed
|
||||
// solely for that crate. Our local `fetch` extension contributes
|
||||
// `src/runtime.js` (pure JS). No `node:` imports happen at snapshot
|
||||
// build time, no `.mjs`, no user-supplied modules. So:
|
||||
// Specialized to our inputs: the deno_* extensions ship `.js`, except
|
||||
// `deno_telemetry` whose lazy scripts are `.ts`. Our local `fetch` extension
|
||||
// contributes `src/runtime.js`. No `node:` imports, no `.mjs`, no
|
||||
// user-supplied modules. So:
|
||||
// - `.js` → pass through.
|
||||
// - `.ts` → transpile via deno_ast (deno_telemetry only).
|
||||
// - anything else → build bug (deno shipping an unexpected file type
|
||||
// or us mislabelling one), panic loudly rather than emit a broken
|
||||
// snapshot.
|
||||
//
|
||||
// No source maps: the snapshot is a binary blob the runtime loads — source
|
||||
// maps would never be consumed.
|
||||
// No source maps: nothing consumes them.
|
||||
//
|
||||
// The signature still returns `Result<_, JsErrorBox>` because that's what
|
||||
// `extension_transpiler` expects, but we never construct one — parse and
|
||||
// transpile failures are build-time bugs in deno's own .ts internals (or
|
||||
// in our runtime.js, if we ever change its extension), so they panic.
|
||||
// The signature returns `Result<_, JsErrorBox>` because that's what
|
||||
// `extension_transpiler` expects, but we never construct one: parse and
|
||||
// transpile failures are build-time bugs, so they panic.
|
||||
//
|
||||
// This replaces a call to `deno_runtime::transpile::maybe_transpile_source`
|
||||
// from `deno_runtime 0.198.0`. The original is more general (handles
|
||||
// `node:` modules, `.mjs`, emits source maps in debug builds, plumbs
|
||||
// errors via `JsErrorBox`); none of that surface is reachable in our
|
||||
// build. Dropping the `deno_runtime` dep eliminates a
|
||||
// `deno_cache → rusqlite → libsqlite3-sys 0.35` transitive chain that
|
||||
// collides with sqlx-sqlite's `libsqlite3-sys 0.30` (cargo's
|
||||
// `links = "sqlite3"` rule).
|
||||
// `deno_runtime::transpile::maybe_transpile_source` is the general version;
|
||||
// depending on `deno_runtime` would pull a
|
||||
// `deno_cache → rusqlite → libsqlite3-sys` chain that collides with
|
||||
// sqlx-sqlite's `libsqlite3-sys` (cargo's `links = "sqlite3"` rule).
|
||||
fn maybe_transpile_source(
|
||||
name: ModuleName,
|
||||
source: ModuleCodeString,
|
||||
@@ -154,7 +49,7 @@ fn maybe_transpile_source(
|
||||
|
||||
let parsed = deno_ast::parse_module(ParseParams {
|
||||
specifier: deno_core::url::Url::parse(&name).unwrap(),
|
||||
text: source.into(),
|
||||
text: source.as_str().into(),
|
||||
media_type,
|
||||
capture_tokens: false,
|
||||
scope_analysis: false,
|
||||
@@ -169,7 +64,13 @@ fn maybe_transpile_source(
|
||||
..Default::default()
|
||||
},
|
||||
&deno_ast::TranspileModuleOptions::default(),
|
||||
&deno_ast::EmitOptions::default(),
|
||||
// The default appends an inline source map comment, which is a
|
||||
// syntax error where deno_core evaluates a lazy script as an
|
||||
// expression.
|
||||
&deno_ast::EmitOptions {
|
||||
source_map: deno_ast::SourceMapOption::None,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|e| panic!("snapshot transpile: emit failed for {name}: {e}"))
|
||||
.into_source();
|
||||
@@ -177,27 +78,66 @@ fn maybe_transpile_source(
|
||||
Ok((transpiled.text.into(), None))
|
||||
}
|
||||
|
||||
/// One `(specifier, source)` table of the extension scripts the snapshot did
|
||||
/// not evaluate. The snapshot only carries scripts that ran while it was built,
|
||||
/// so a script first loaded in a live isolate has to come from here.
|
||||
///
|
||||
/// deno_core binary-searches the table and reads each source as ASCII without
|
||||
/// checking, so it must be sorted by specifier, free of duplicates, and ASCII.
|
||||
fn residual_table(
|
||||
name: &str,
|
||||
files: impl Iterator<Item = deno_core::ExtensionFileSource>,
|
||||
consumed: &HashSet<String>,
|
||||
) -> String {
|
||||
let mut files: Vec<_> = files.filter(|f| !consumed.contains(f.specifier)).collect();
|
||||
files.sort_by_key(|f| f.specifier);
|
||||
files.dedup_by_key(|f| f.specifier);
|
||||
|
||||
let mut out = format!("pub static {name}: &[(&str, &str)] = &[\n");
|
||||
for file in files {
|
||||
let source = file
|
||||
.load()
|
||||
.unwrap_or_else(|e| panic!("could not load {}: {e}", file.specifier));
|
||||
let (code, _) =
|
||||
maybe_transpile_source(ModuleName::from_static(file.specifier), source).unwrap();
|
||||
assert!(
|
||||
code.as_str().is_ascii(),
|
||||
"{} is not ASCII after transpiling",
|
||||
file.specifier
|
||||
);
|
||||
writeln!(out, " ({:?}, {:?}),", file.specifier, code.as_str()).unwrap();
|
||||
}
|
||||
out.push_str("];\n");
|
||||
out
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
|
||||
println!("cargo:rustc-env=PROFILE={}", env::var("PROFILE").unwrap());
|
||||
|
||||
let exts = vec![
|
||||
deno_telemetry::deno_telemetry::init(),
|
||||
deno_webidl::deno_webidl::init(),
|
||||
deno_url::deno_url::init(),
|
||||
deno_console::deno_console::init(),
|
||||
deno_web::deno_web::init::<PermissionsContainer>(Arc::new(BlobStore::default()), None),
|
||||
deno_crypto::deno_crypto::init(None),
|
||||
deno_fetch::deno_fetch::init::<PermissionsContainer>(Default::default()),
|
||||
deno_net::deno_net::init::<PermissionsContainer>(None, None),
|
||||
// Same list and order as `create_nativets_runtime`, which supplies their
|
||||
// state through `lazy_init_extensions`.
|
||||
let exts: Vec<Extension> = vec![
|
||||
deno_telemetry::deno_telemetry::lazy_init(),
|
||||
deno_webidl::deno_webidl::lazy_init(),
|
||||
deno_web::deno_web::lazy_init(),
|
||||
deno_crypto::deno_crypto::lazy_init(),
|
||||
deno_fetch::deno_fetch::lazy_init(),
|
||||
deno_net::deno_net::lazy_init(),
|
||||
fetch::init(),
|
||||
];
|
||||
|
||||
// Build the file path to the snapshot.
|
||||
let lazy_js: Vec<_> = exts
|
||||
.iter()
|
||||
.flat_map(|e| e.lazy_loaded_js_files.iter().cloned())
|
||||
.collect();
|
||||
let lazy_esm: Vec<_> = exts
|
||||
.iter()
|
||||
.flat_map(|e| e.lazy_loaded_esm_files.iter().cloned())
|
||||
.collect();
|
||||
|
||||
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
let snapshot_path = o.join("FETCH_SNAPSHOT.bin");
|
||||
|
||||
// Create the snapshot.
|
||||
let output = deno_core::snapshot::create_snapshot(
|
||||
deno_core::snapshot::CreateSnapshotOptions {
|
||||
cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
|
||||
@@ -213,9 +153,17 @@ fn main() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut file = std::fs::File::create(snapshot_path).unwrap();
|
||||
let mut file = std::fs::File::create(o.join("FETCH_SNAPSHOT.bin")).unwrap();
|
||||
file.write_all(&output.output).unwrap();
|
||||
|
||||
let consumed: HashSet<String> = output.consumed_lazy_specifiers.into_iter().collect();
|
||||
let residual = format!(
|
||||
"{}{}",
|
||||
residual_table("RESIDUAL_LAZY_JS", lazy_js.into_iter(), &consumed),
|
||||
residual_table("RESIDUAL_LAZY_ESM", lazy_esm.into_iter(), &consumed),
|
||||
);
|
||||
std::fs::write(o.join("residual_lazy_sources.rs"), residual).unwrap();
|
||||
|
||||
for path in output.files_loaded_during_snapshot {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ mod smoke_tests;
|
||||
mod cert_tests;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{Arc, LazyLock, Mutex},
|
||||
};
|
||||
@@ -32,17 +30,55 @@ use std::{
|
||||
// Re-export deno_telemetry for use by windmill-worker's otel proxy
|
||||
pub use deno_telemetry;
|
||||
|
||||
/// The real environment and filesystem, except that the OTLP protocol always
|
||||
/// reads as HTTP.
|
||||
///
|
||||
/// The caller points `OTEL_EXPORTER_OTLP_ENDPOINT` at its own HTTP collector
|
||||
/// only while [`init_telemetry`] runs, and the instance's own exporter shares
|
||||
/// `OTEL_EXPORTER_OTLP_PROTOCOL`. Honouring `grpc` there would build an exporter
|
||||
/// that reads the endpoint again on every export, and `console` one that never
|
||||
/// reads it, so either would send job telemetry past that collector.
|
||||
struct HttpOtlpSys;
|
||||
|
||||
impl sys_traits::BaseEnvVar for HttpOtlpSys {
|
||||
fn base_env_var_os(&self, key: &std::ffi::OsStr) -> Option<std::ffi::OsString> {
|
||||
if key == "OTEL_EXPORTER_OTLP_PROTOCOL" {
|
||||
return Some("http/protobuf".into());
|
||||
}
|
||||
sys_traits::impls::RealSys.base_env_var_os(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl sys_traits::BaseFsRead for HttpOtlpSys {
|
||||
fn base_fs_read(
|
||||
&self,
|
||||
path: &std::path::Path,
|
||||
) -> std::io::Result<std::borrow::Cow<'static, [u8]>> {
|
||||
sys_traits::impls::RealSys.base_fs_read(path)
|
||||
}
|
||||
}
|
||||
|
||||
/// `deno_telemetry::init` for the isolates of this process.
|
||||
pub fn init_telemetry(
|
||||
rt_config: deno_telemetry::OtelRuntimeConfig,
|
||||
config: deno_telemetry::OtelConfig,
|
||||
) -> anyhow::Result<()> {
|
||||
deno_telemetry::init(&HttpOtlpSys, rt_config, config)
|
||||
}
|
||||
|
||||
use deno_ast::ParseParams;
|
||||
use deno_core::{
|
||||
error::CoreErrorKind,
|
||||
op2, serde_v8, url,
|
||||
v8::{self, IsolateHandle},
|
||||
Extension, JsRuntime, OpState, PollEventLoopOptions, RuntimeOptions,
|
||||
};
|
||||
use deno_error::JsErrorBox;
|
||||
use deno_fetch::FetchPermissions;
|
||||
use deno_net::NetPermissions;
|
||||
use deno_permissions::{
|
||||
Permissions, PermissionsContainer, PermissionsOptions, RuntimePermissionDescriptorParser,
|
||||
};
|
||||
use deno_tls::{rustls::pki_types::CertificateDer, rustls::RootCertStore, RootCertStoreProvider};
|
||||
use deno_web::{BlobStore, TimersPermission};
|
||||
use deno_web::{BlobStore, InMemoryBroadcastChannel};
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
@@ -57,156 +93,55 @@ use windmill_common::worker::{write_file, Connection, WINDMILL_DIR};
|
||||
|
||||
// ── Snapshot-matched extensions ──────────────────────────────────────
|
||||
//
|
||||
// `deno_core` 0.352 validates that the snapshot's extension list is a
|
||||
// *prefix* of the runtime's extension list (snapshot does not need an
|
||||
// exact match — runtime is allowed to add extensions at the tail, but
|
||||
// must not reorder or omit any that the snapshot baked in).
|
||||
// The snapshot (build.rs) is built from the deno_* extensions in their
|
||||
// `lazy_init()` form followed by this local `fetch` ext. deno_core requires the
|
||||
// runtime's extension list to start with that same list in that same order; it
|
||||
// may append more, which is how the windmill `ext` carrying our own ops gets in.
|
||||
//
|
||||
// Our snapshot (in build.rs) is the same eight deno_* extensions ending
|
||||
// with this local `fetch` ext. The runtime adds one extra entry at the
|
||||
// end — the windmill `ext` carrying our own ops — which is fine because
|
||||
// it's after the snapshot prefix.
|
||||
//
|
||||
// This local `fetch` extension declaration must be present in both
|
||||
// build.rs and lib.rs so the type passes through the `init()` macro.
|
||||
// The ESM is already in the snapshot, so this `init()` call at runtime
|
||||
// is a no-op for esm — the registration just records the ext.
|
||||
// This declaration must exist in both build.rs and lib.rs. The ESM is already
|
||||
// in the snapshot, so `init()` at runtime only records the extension.
|
||||
deno_core::extension!(
|
||||
fetch,
|
||||
esm_entry_point = "ext:fetch/src/runtime.js",
|
||||
esm = ["src/runtime.js"],
|
||||
);
|
||||
|
||||
// ── Permission container ─────────────────────────────────────────────
|
||||
include!(concat!(env!("OUT_DIR"), "/residual_lazy_sources.rs"));
|
||||
|
||||
/// `no_network` is what `//no_network` promises: no outbound traffic of any kind,
|
||||
/// the Windmill API included (the client module reaches it through `fetch`).
|
||||
/// Enforced here and by disabling the deno_net ops, never in JS: user code can
|
||||
/// call `Deno.core.ops.*` directly and skip any JS-level guard.
|
||||
pub struct PermissionsContainer {
|
||||
pub no_network: bool,
|
||||
}
|
||||
|
||||
const NO_NETWORK_ANNOTATION: &str = "no_network";
|
||||
// ── Permissions ──────────────────────────────────────────────────────
|
||||
|
||||
/// Every op deno_net registers, see `create_nativets_runtime`. Read from the
|
||||
/// extension itself so an upgrade that adds an op cannot leave it enabled.
|
||||
static NO_NETWORK_DISABLED_OPS: LazyLock<std::collections::HashSet<&'static str>> =
|
||||
LazyLock::new(|| {
|
||||
deno_net::deno_net::init::<PermissionsContainer>(None, None)
|
||||
deno_net::deno_net::lazy_init()
|
||||
.ops
|
||||
.iter()
|
||||
.map(|op| op.name)
|
||||
.collect()
|
||||
});
|
||||
|
||||
impl PermissionsContainer {
|
||||
fn deny_net(&self, target: &str) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
if self.no_network {
|
||||
Err(deno_permissions::PermissionDeniedError::Fatal {
|
||||
access: format!(
|
||||
"net access to {target} (the script is annotated //{NO_NETWORK_ANNOTATION})"
|
||||
),
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FetchPermissions for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn check_net_url(
|
||||
&mut self,
|
||||
url: &deno_core::url::Url,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("\"{}\"", url.host_str().unwrap_or_default()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_read<'a>(
|
||||
&mut self,
|
||||
path: Cow<'a, std::path::Path>,
|
||||
_api_name: &str,
|
||||
_get_path: &'a dyn deno_fs::GetPath,
|
||||
) -> Result<deno_fs::CheckedPath<'a>, deno_io::fs::FsError> {
|
||||
Ok(deno_fs::CheckedPath::Unresolved(path))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_write<'a>(
|
||||
&mut self,
|
||||
path: Cow<'a, std::path::Path>,
|
||||
_api_name: &str,
|
||||
_get_path: &'a dyn deno_fs::GetPath,
|
||||
) -> Result<deno_fs::CheckedPath<'a>, deno_io::fs::FsError> {
|
||||
Ok(deno_fs::CheckedPath::Unresolved(path))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_net_vsock(
|
||||
&mut self,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("vsock {cid}:{port}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl TimersPermission for PermissionsContainer {
|
||||
#[inline(always)]
|
||||
fn allow_hrtime(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl NetPermissions for PermissionsContainer {
|
||||
fn check_read(
|
||||
&mut self,
|
||||
p: &str,
|
||||
_api_name: &str,
|
||||
) -> Result<PathBuf, deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("unix socket \"{p}\""))?;
|
||||
Ok(PathBuf::from(p))
|
||||
}
|
||||
|
||||
fn check_write(
|
||||
&mut self,
|
||||
p: &str,
|
||||
_api_name: &str,
|
||||
) -> Result<PathBuf, deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("unix socket \"{p}\""))?;
|
||||
Ok(PathBuf::from(p))
|
||||
}
|
||||
|
||||
fn check_net<T: AsRef<str>>(
|
||||
&mut self,
|
||||
host: &(T, Option<u16>),
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("\"{}\"", host.0.as_ref()))
|
||||
}
|
||||
|
||||
fn check_write_path<'a>(
|
||||
&mut self,
|
||||
p: Cow<'a, std::path::Path>,
|
||||
_api_name: &str,
|
||||
) -> Result<Cow<'a, std::path::Path>, deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("unix socket \"{}\"", p.display()))?;
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
fn check_vsock(
|
||||
&mut self,
|
||||
cid: u32,
|
||||
port: u32,
|
||||
_api_name: &str,
|
||||
) -> Result<(), deno_permissions::PermissionCheckError> {
|
||||
self.deny_net(&format!("vsock {cid}:{port}"))
|
||||
}
|
||||
/// What an isolate may do: reach the network, unless `no_network`, and nothing
|
||||
/// else (no filesystem, environment, subprocess or FFI). Nothing prompts.
|
||||
///
|
||||
/// `no_network` is what `//no_network` promises: no outbound traffic of any
|
||||
/// kind, the Windmill API included (the client module reaches it through
|
||||
/// `fetch`). Enforced here and by disabling the deno_net ops, never in JS: user
|
||||
/// code can call `Deno.core.ops.*` directly and skip any JS-level guard.
|
||||
fn isolate_permissions(no_network: bool) -> anyhow::Result<PermissionsContainer> {
|
||||
let parser = Arc::new(RuntimePermissionDescriptorParser::new(
|
||||
sys_traits::impls::RealSys,
|
||||
));
|
||||
// An empty list means "all hosts", for a grant and for a denial alike.
|
||||
let options = PermissionsOptions {
|
||||
allow_net: (!no_network).then(Vec::new),
|
||||
deny_net: no_network.then(Vec::new),
|
||||
prompt: false,
|
||||
..Default::default()
|
||||
};
|
||||
let permissions = Permissions::from_options(parser.as_ref(), &options)
|
||||
.map_err(|e| anyhow::anyhow!("invalid isolate permissions: {e}"))?;
|
||||
Ok(PermissionsContainer::new(parser, permissions))
|
||||
}
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────
|
||||
@@ -228,7 +163,7 @@ pub struct NativeAnnotation {
|
||||
/// script; `None` leaves the default in force.
|
||||
pub fetch_response_timeout_secs: Option<u64>,
|
||||
/// `//no_network`: the isolate may open no connection at all, see
|
||||
/// [`PermissionsContainer`].
|
||||
/// [`isolate_permissions`].
|
||||
pub no_network: bool,
|
||||
}
|
||||
|
||||
@@ -413,13 +348,10 @@ pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
// deno_fetch requires a TLS provider; install ring as default (idempotent).
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
|
||||
let unrecognized_v8_flags = deno_core::v8_set_flags(vec![
|
||||
"--stack-size=1024".to_string(),
|
||||
"--no-harmony-import-assertions".to_string(),
|
||||
])
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>();
|
||||
let unrecognized_v8_flags = deno_core::v8_set_flags(vec!["--stack-size=1024".to_string()])
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !unrecognized_v8_flags.is_empty() {
|
||||
init_err = Some(format!(
|
||||
@@ -435,7 +367,7 @@ pub fn setup_deno_runtime() -> anyhow::Result<()> {
|
||||
// SIGSEGV in WasmCodePointerTable::AllocateUninitializedEntry() on x86_64 Linux.
|
||||
// See: https://github.com/denoland/deno_core/issues/952
|
||||
let platform = deno_core::v8::new_unprotected_default_platform(0, false).make_shared();
|
||||
deno_core::JsRuntime::init_platform(Some(platform), false);
|
||||
deno_core::JsRuntime::init_platform(Some(platform));
|
||||
});
|
||||
|
||||
if let Some(msg) = init_err {
|
||||
@@ -666,17 +598,15 @@ pub(crate) fn create_nativets_runtime(
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Same list and order as the snapshot (build.rs); their state follows in
|
||||
// `lazy_init_extensions` below, which must match it too.
|
||||
let exts: Vec<Extension> = vec![
|
||||
deno_telemetry::deno_telemetry::init(),
|
||||
deno_webidl::deno_webidl::init(),
|
||||
deno_url::deno_url::init(),
|
||||
deno_console::deno_console::init(),
|
||||
deno_web::deno_web::init::<PermissionsContainer>(Arc::new(BlobStore::default()), None),
|
||||
// Registered after deno_web to keep the snapshot (build.rs) a prefix of
|
||||
// the runtime extension list; deno_crypto declares deps = [deno_webidl, deno_web].
|
||||
deno_crypto::deno_crypto::init(None),
|
||||
deno_fetch::deno_fetch::init::<PermissionsContainer>(fetch_options),
|
||||
deno_net::deno_net::init::<PermissionsContainer>(None, None),
|
||||
deno_telemetry::deno_telemetry::lazy_init(),
|
||||
deno_webidl::deno_webidl::lazy_init(),
|
||||
deno_web::deno_web::lazy_init(),
|
||||
deno_crypto::deno_crypto::lazy_init(),
|
||||
deno_fetch::deno_fetch::lazy_init(),
|
||||
deno_net::deno_net::lazy_init(),
|
||||
fetch::init(),
|
||||
ext,
|
||||
];
|
||||
@@ -688,6 +618,8 @@ pub(crate) fn create_nativets_runtime(
|
||||
deno_core::v8::CreateParams::default().heap_limits(0, 1024 * 1024 * 128),
|
||||
),
|
||||
startup_snapshot: Some(RUNTIME_SNAPSHOT),
|
||||
residual_lazy_js_sources: RESIDUAL_LAZY_JS,
|
||||
residual_lazy_esm_sources: RESIDUAL_LAZY_ESM,
|
||||
module_loader: Some(Rc::new(deno_core::FsModuleLoader)),
|
||||
extension_transpiler: None,
|
||||
..Default::default()
|
||||
@@ -714,12 +646,28 @@ pub(crate) fn create_nativets_runtime(
|
||||
y * 2
|
||||
});
|
||||
|
||||
js_runtime
|
||||
.lazy_init_extensions(vec![
|
||||
deno_telemetry::deno_telemetry::args(),
|
||||
deno_webidl::deno_webidl::args(),
|
||||
deno_web::deno_web::args(
|
||||
Arc::new(BlobStore::default()),
|
||||
None,
|
||||
false,
|
||||
InMemoryBroadcastChannel::default(),
|
||||
),
|
||||
deno_crypto::deno_crypto::args(None),
|
||||
deno_fetch::deno_fetch::args(fetch_options),
|
||||
deno_net::deno_net::args(None, None),
|
||||
])
|
||||
.map_err(windmill_common::error::to_anyhow)?;
|
||||
|
||||
let (log_sender, log_receiver) = mpsc::unbounded_channel::<String>();
|
||||
|
||||
{
|
||||
let op_state = js_runtime.op_state();
|
||||
let mut op_state = op_state.borrow_mut();
|
||||
op_state.put(PermissionsContainer { no_network });
|
||||
op_state.put(isolate_permissions(no_network)?);
|
||||
op_state.put(MainArgs { args: initial_args });
|
||||
op_state.put(LogString { s: log_sender });
|
||||
}
|
||||
@@ -731,7 +679,7 @@ pub(crate) fn create_nativets_runtime(
|
||||
.execute_script(
|
||||
"<wm_init>",
|
||||
format!(
|
||||
"globalThis.__wmInitPerIsolate({{ fetchResponseTimeoutMs: {fetch_response_timeout_ms} }})"
|
||||
"globalThis.__wmInitPerIsolate({{ fetchResponseTimeoutMs: {fetch_response_timeout_ms}, noNetwork: {no_network} }})"
|
||||
),
|
||||
)
|
||||
.map_err(windmill_common::error::to_anyhow)?;
|
||||
@@ -777,7 +725,7 @@ pub(crate) fn extract_global_string(
|
||||
js_runtime: &mut JsRuntime,
|
||||
global: v8::Global<v8::Value>,
|
||||
) -> Result<Box<RawValue>, String> {
|
||||
let scope = &mut js_runtime.handle_scope();
|
||||
deno_core::scope!(scope, js_runtime);
|
||||
let local = v8::Local::new(scope, global);
|
||||
match serde_v8::from_v8::<Option<String>>(scope, local) {
|
||||
Ok(s) => Ok(unsafe_raw(s.unwrap_or_else(|| "null".to_string()))),
|
||||
@@ -1169,7 +1117,10 @@ import("file:///eval.ts").then((module) => module.{main_fn}(...args))
|
||||
Ok(global) => {
|
||||
extract_global_string(js_runtime, global).map_err(|e| ExecuteError::Script(e))
|
||||
}
|
||||
Err(deno_core::error::CoreError::Js(e)) => {
|
||||
Err(e) if matches!(e.as_kind(), CoreErrorKind::Js(_)) => {
|
||||
let CoreErrorKind::Js(e) = e.into_kind() else {
|
||||
unreachable!("matched as a JS error above")
|
||||
};
|
||||
let source = e.frames.first().and_then(|f| {
|
||||
f.file_name
|
||||
.as_ref()
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
|
||||
import * as domException from "ext:deno_web/01_dom_exception.js";
|
||||
import * as base64 from "ext:deno_web/05_base64.js";
|
||||
import * as console from "ext:deno_console/01_console.js";
|
||||
import * as encoding from "ext:deno_web/08_text_encoding.js";
|
||||
import * as event from "ext:deno_web/02_event.js";
|
||||
import * as fetch from "ext:deno_fetch/26_fetch.js";
|
||||
import * as file from "ext:deno_web/09_file.js";
|
||||
import * as fileReader from "ext:deno_web/10_filereader.js";
|
||||
import * as formData from "ext:deno_fetch/21_formdata.js";
|
||||
import * as headers from "ext:deno_fetch/20_headers.js";
|
||||
import * as streams from "ext:deno_web/06_streams.js";
|
||||
import * as timers from "ext:deno_web/02_timers.js";
|
||||
import * as url from "ext:deno_url/00_url.js";
|
||||
import * as net from "ext:deno_net/01_net.js";
|
||||
import * as tls from "ext:deno_net/02_tls.js";
|
||||
import * as urlPattern from "ext:deno_url/01_urlpattern.js";
|
||||
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
||||
import * as crypto from "ext:deno_crypto/00_crypto.js";
|
||||
import * as response from "ext:deno_fetch/23_response.js";
|
||||
import * as request from "ext:deno_fetch/23_request.js";
|
||||
import "ext:deno_web/02_structured_clone.js";
|
||||
import * as globalInterfaces from "ext:deno_web/04_global_interfaces.js";
|
||||
// Namespace imports (not side-effect-only) so their constructors are reachable
|
||||
// for the globalThis wiring below. The module bodies still execute on
|
||||
// evaluation, so their side effects apply.
|
||||
import * as messagePort from "ext:deno_web/13_message_port.js";
|
||||
import * as compression from "ext:deno_web/14_compression.js";
|
||||
import * as performance from "ext:deno_web/15_performance.js";
|
||||
import "ext:deno_web/16_image_data.js";
|
||||
import "ext:deno_fetch/27_eventsource.js";
|
||||
import { core, internals } from "ext:core/mod.js";
|
||||
|
||||
// deno_crypto brands CryptoKeys with this symbol and reads it from `internals`
|
||||
// when it evaluates; in Deno a deno_node module sets it, which is not loaded here.
|
||||
internals.kKeyObject ??= Symbol("kKeyObject");
|
||||
|
||||
// Every script loaded here runs at snapshot-build time and is baked into the
|
||||
// snapshot. One that is first loaded at run time comes from the residual table
|
||||
// build.rs emits instead.
|
||||
const abortSignal = core.loadExtScript("ext:deno_web/03_abort_signal.js");
|
||||
const domException = core.loadExtScript("ext:deno_web/01_dom_exception.js");
|
||||
const base64 = core.loadExtScript("ext:deno_web/05_base64.js");
|
||||
const console = core.loadExtScript("ext:deno_web/01_console.js");
|
||||
const encoding = core.loadExtScript("ext:deno_web/08_text_encoding.js");
|
||||
const event = core.loadExtScript("ext:deno_web/02_event.js");
|
||||
const fetch = core.loadExtScript("ext:deno_fetch/26_fetch.js");
|
||||
const file = core.loadExtScript("ext:deno_web/09_file.js");
|
||||
const fileReader = core.loadExtScript("ext:deno_web/10_filereader.js");
|
||||
const formData = core.loadExtScript("ext:deno_fetch/21_formdata.js");
|
||||
const headers = core.loadExtScript("ext:deno_fetch/20_headers.js");
|
||||
const streams = core.loadExtScript("ext:deno_web/06_streams.js");
|
||||
const timers = core.loadExtScript("ext:deno_web/02_timers.js");
|
||||
const url = core.loadExtScript("ext:deno_web/00_url.js");
|
||||
const urlPattern = core.loadExtScript("ext:deno_web/01_urlpattern.js");
|
||||
const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js");
|
||||
const crypto = core.loadExtScript("ext:deno_crypto/00_crypto.js");
|
||||
const response = core.loadExtScript("ext:deno_fetch/23_response.js");
|
||||
const request = core.loadExtScript("ext:deno_fetch/23_request.js");
|
||||
const globalInterfaces = core.loadExtScript(
|
||||
"ext:deno_web/04_global_interfaces.js",
|
||||
);
|
||||
const messagePort = core.loadExtScript("ext:deno_web/13_message_port.js");
|
||||
const compression = core.loadExtScript("ext:deno_web/14_compression.js");
|
||||
const performance = core.loadExtScript("ext:deno_web/15_performance.js");
|
||||
|
||||
// deno_fetch applies no deadline, so a peer that accepts a request and then
|
||||
// never answers leaves `await fetch(...)` pending until the job timeout, which
|
||||
// self-hosted defaults to 7 days.
|
||||
const ORIGINAL_FETCH = fetch.fetch;
|
||||
const DENO_FETCH = fetch.fetch;
|
||||
|
||||
// deno_web's timers reject any `this` other than undefined/globalThis, so
|
||||
// `timers.setTimeout(...)` passes the module namespace and throws "Illegal
|
||||
@@ -50,6 +53,14 @@ const abortControllerAbort = Function.prototype.call.bind(
|
||||
);
|
||||
const ReflectApply = Reflect.apply;
|
||||
|
||||
// Forwards the original argument count, like every caller below relies on. The
|
||||
// extra promise hop under `no_network` costs the same-tick settling of an
|
||||
// aborted fetch, which only matters where a request could have been made.
|
||||
function ORIGINAL_FETCH() {
|
||||
const p = ReflectApply(DENO_FETCH, undefined, arguments);
|
||||
return noNetwork ? promiseThen(p, undefined, rewordNoNetworkDenial) : p;
|
||||
}
|
||||
|
||||
// Installed per isolate by __wmInitPerIsolate; 0 disables. Only a backstop
|
||||
// for the impossible case of fetch running before that init.
|
||||
let fetchResponseTimeoutMs = 900_000;
|
||||
@@ -74,6 +85,21 @@ function fetchResponseTimeoutError(requestUrl, timeoutMs) {
|
||||
);
|
||||
}
|
||||
|
||||
// Set per isolate for a `//no_network` script. The denial itself is the
|
||||
// isolate's permissions (Rust); this only replaces deno's "run again with the
|
||||
// --allow-net flag" hint, which names a flag a script author cannot pass.
|
||||
let noNetwork = false;
|
||||
|
||||
function rewordNoNetworkDenial(e) {
|
||||
if (e?.name === "NotCapable" && typeof e.message === "string") {
|
||||
e.message = e.message.replace(
|
||||
", run again with the --allow-net flag",
|
||||
": the script is annotated //no_network",
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
globalThis.atob = base64.atob;
|
||||
globalThis.btoa = base64.btoa;
|
||||
// Not `async`, for the same reason deno_fetch's own outer fetch isn't: WPT
|
||||
@@ -152,7 +178,22 @@ globalThis.console = new console.Console((msg, level) =>
|
||||
);
|
||||
globalThis.AbortController = abortSignal.AbortController;
|
||||
globalThis.AbortSignal = abortSignal.AbortSignal;
|
||||
globalThis.crypto = crypto.crypto;
|
||||
// A getter, not a value: `crypto.crypto` mints a cppgc object, and the cppgc
|
||||
// heap is not attached while this module runs at snapshot-build time.
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => crypto.crypto,
|
||||
// Assignment has to keep working, as it does on a plain property.
|
||||
set(value) {
|
||||
Object.defineProperty(globalThis, "crypto", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value,
|
||||
});
|
||||
},
|
||||
});
|
||||
globalThis.Crypto = crypto.Crypto;
|
||||
globalThis.CryptoKey = crypto.CryptoKey;
|
||||
globalThis.SubtleCrypto = crypto.SubtleCrypto;
|
||||
@@ -233,6 +274,7 @@ globalThis.__wmInitPerIsolate = (config) => {
|
||||
if (config != null && typeof config.fetchResponseTimeoutMs === "number") {
|
||||
fetchResponseTimeoutMs = config.fetchResponseTimeoutMs;
|
||||
}
|
||||
noNetwork = config?.noNetwork === true;
|
||||
|
||||
// setTimeOrigin() seeds performance.timeOrigin from the isolate's wall clock;
|
||||
// without it timeOrigin is undefined and `timeOrigin + performance.now()` is NaN.
|
||||
@@ -256,13 +298,14 @@ globalThis.__wmInitPerIsolate = (config) => {
|
||||
};
|
||||
|
||||
// Expose bootstrapOtel globally so it can be called from Rust after runtime creation.
|
||||
// We use dynamic import so deno_telemetry isn't loaded during snapshot creation.
|
||||
// Loaded on first call so deno_telemetry isn't evaluated during snapshot creation.
|
||||
// Config: [tracingEnabled, metricsEnabled, consoleConfig, deterministic]
|
||||
// consoleConfig: 0=ignore, 1=capture, 2=replace
|
||||
globalThis.__bootstrapOtel = () => {
|
||||
import("ext:deno_telemetry/telemetry.ts").then(({ bootstrap, enterSpan }) => {
|
||||
bootstrap([1, 0, 1, 0]);
|
||||
// Expose enterSpan for setting parent trace context
|
||||
globalThis.__enterSpan = enterSpan;
|
||||
});
|
||||
const { bootstrap, enterSpan } = core.loadExtScript(
|
||||
"ext:deno_telemetry/telemetry.ts",
|
||||
);
|
||||
bootstrap([1, 0, 1, 0]);
|
||||
// Expose enterSpan for setting parent trace context
|
||||
globalThis.__enterSpan = enterSpan;
|
||||
};
|
||||
|
||||
@@ -451,6 +451,10 @@ export async function main(): Promise<{ uuid: string; nonzero: boolean; sha256:
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
|
||||
// `crypto` is installed as an accessor; polyfill-style assignment must
|
||||
// still work rather than throw for want of a setter.
|
||||
(globalThis as any).crypto = globalThis.crypto;
|
||||
|
||||
return { uuid, nonzero, sha256 };
|
||||
}
|
||||
"#;
|
||||
|
||||
@@ -55,6 +55,9 @@ async function attempt(f) {{
|
||||
export async function main() {{
|
||||
return {{
|
||||
fetch: await attempt(() => fetch("http://127.0.0.1:{port}/").then((r) => r.text())),
|
||||
data_url: await attempt(() => fetch("data:text/plain,ok").then((r) => r.text())),
|
||||
blob_url: await attempt(() => fetch(URL.createObjectURL(new Blob(["ok"]))).then((r) => r.text())),
|
||||
raw_fetch: await attempt(() => Deno.core.ops.op_fetch("GET", "http://127.0.0.1:{port}/", [], null, false, null, null)),
|
||||
tcp: await attempt(() => Deno.core.ops.op_net_connect_tcp({{ hostname: "127.0.0.1", port: {port} }}, null, null)),
|
||||
unix: await attempt(() => Deno.core.ops.op_net_connect_unix("/var/run/docker.sock")),
|
||||
quic: await attempt(() => Deno.core.ops.op_quic_endpoint_create({{ hostname: "localhost", port: 0 }}, true)),
|
||||
@@ -76,6 +79,15 @@ async fn no_network_denies_fetch_and_raw_socket_ops() {
|
||||
fetch.contains("no_network"),
|
||||
"fetch was not denied: {fetch}"
|
||||
);
|
||||
// Neither opens a connection, so neither is network access.
|
||||
assert_eq!(out["data_url"], "allowed", "{out}");
|
||||
assert_eq!(out["blob_url"], "allowed", "{out}");
|
||||
// The global `fetch` only words the refusal; the op under it is what refuses.
|
||||
let raw_fetch = out["raw_fetch"].as_str().unwrap();
|
||||
assert!(
|
||||
raw_fetch.contains("Requires net access"),
|
||||
"the fetch op was not denied: {raw_fetch}"
|
||||
);
|
||||
for key in ["tcp", "unix", "quic"] {
|
||||
let msg = out[key].as_str().unwrap();
|
||||
assert!(
|
||||
|
||||
@@ -71,7 +71,7 @@ async fn spawn_capturing_http(captured: Arc<Mutex<Vec<Vec<u8>>>>) -> u16 {
|
||||
/// drifts away from `tracing_enabled + Capture`, this test breaks
|
||||
/// before the customer-facing panic does.
|
||||
fn init_with_ee_otel_config() {
|
||||
deno_telemetry::init(
|
||||
windmill_runtime_nativets::init_telemetry(
|
||||
deno_telemetry::OtelRuntimeConfig {
|
||||
runtime_name: "windmill-nativets".into(),
|
||||
runtime_version: "0".into(),
|
||||
@@ -157,13 +157,12 @@ export async function main(): Promise<number> {{
|
||||
"fetch target should have been hit exactly once"
|
||||
);
|
||||
|
||||
// 4. Force the BatchSpanProcessor to flush so the exporter posts
|
||||
// to our mock collector synchronously (default flush interval
|
||||
// is ~5s; tests can't wait that long).
|
||||
deno_telemetry::flush();
|
||||
// Exporter is async over the OTel runtime; give it a beat to
|
||||
// actually send the HTTP request.
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
// 4. The BatchSpanProcessor exports on its own schedule (about 5s by
|
||||
// default) and deno_telemetry exposes no flush, so wait for it.
|
||||
let deadline = tokio::time::Instant::now() + Duration::from_secs(20);
|
||||
while otlp_hits.lock().await.is_empty() && tokio::time::Instant::now() < deadline {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
let otlp_captured = otlp_hits.lock().await;
|
||||
assert!(
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn ee_otel_init_config_populates_otel_globals() {
|
||||
// Step 1: reproduce the footgun. `OtelConfig::default()` has
|
||||
// tracing/metrics off and `console = Ignore`, which trips the
|
||||
// 0.31 early-return — Ok(()) but OnceCell stays empty.
|
||||
deno_telemetry::init(
|
||||
windmill_runtime_nativets::init_telemetry(
|
||||
deno_telemetry::OtelRuntimeConfig {
|
||||
runtime_name: "windmill-nativets-test".into(),
|
||||
runtime_version: "0".into(),
|
||||
@@ -63,10 +63,17 @@ async fn ee_otel_init_config_populates_otel_globals() {
|
||||
tracing_enabled/console fields."
|
||||
);
|
||||
|
||||
// The instance's own exporter shares this variable and may be set to gRPC,
|
||||
// or to anything else. Native telemetry must not read it: a value
|
||||
// deno_telemetry rejects outright shows it is never consulted.
|
||||
// SAFETY: the only test in this binary, so nothing reads the environment
|
||||
// concurrently.
|
||||
unsafe { std::env::set_var("OTEL_EXPORTER_OTLP_PROTOCOL", "not-a-protocol") };
|
||||
|
||||
// Step 2: re-call init with the exact config shape that the EE
|
||||
// `load_internal_otel_exporter` ships — this is what production
|
||||
// hits, so the test exercises the actual prod call path.
|
||||
deno_telemetry::init(
|
||||
windmill_runtime_nativets::init_telemetry(
|
||||
deno_telemetry::OtelRuntimeConfig {
|
||||
runtime_name: "windmill-nativets".into(),
|
||||
runtime_version: "0".into(),
|
||||
|
||||
@@ -215,6 +215,8 @@ lazy_static::lazy_static! {
|
||||
pub static ref PY311_CACHE_DIR: String = format!("{}python_3_11", *ROOT_CACHE_DIR);
|
||||
pub static ref PY312_CACHE_DIR: String = format!("{}python_3_12", *ROOT_CACHE_DIR);
|
||||
pub static ref PY313_CACHE_DIR: String = format!("{}python_3_13", *ROOT_CACHE_DIR);
|
||||
pub static ref PY314_CACHE_DIR: String = format!("{}python_3_14", *ROOT_CACHE_DIR);
|
||||
pub static ref PY315_CACHE_DIR: String = format!("{}python_3_15", *ROOT_CACHE_DIR);
|
||||
|
||||
pub static ref TAR_JAVA_CACHE_DIR: String = format!("{}tar/java", *ROOT_CACHE_DIR);
|
||||
|
||||
|
||||
@@ -1244,6 +1244,7 @@ pub async fn handle_flow_dependency_job(
|
||||
&job.workspace_id,
|
||||
flow.failure_module.as_ref(),
|
||||
flow.same_worker,
|
||||
flow.preserve_step_tags,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -2134,6 +2135,7 @@ async fn insert_flow_modules<'c>(
|
||||
workspace_id: &str,
|
||||
failure_module: Option<&Box<FlowModule>>,
|
||||
same_worker: bool,
|
||||
preserve_step_tags: bool,
|
||||
modules: &mut Vec<FlowModule>,
|
||||
modules_node: &mut Option<FlowNodeId>,
|
||||
) -> Result<sqlx::Transaction<'c, sqlx::Postgres>> {
|
||||
@@ -2144,6 +2146,7 @@ async fn insert_flow_modules<'c>(
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
))
|
||||
.await?;
|
||||
if modules.is_empty() || crate::worker_flow::is_simple_modules(modules, failure_module) {
|
||||
@@ -2160,6 +2163,7 @@ async fn insert_flow_modules<'c>(
|
||||
modules: std::mem::take(modules),
|
||||
failure_module: failure_module.cloned(),
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
..Default::default()
|
||||
}))),
|
||||
None,
|
||||
@@ -2176,6 +2180,7 @@ async fn reduce_flow<'c>(
|
||||
workspace_id: &str,
|
||||
failure_module: Option<&Box<FlowModule>>,
|
||||
same_worker: bool,
|
||||
preserve_step_tags: bool,
|
||||
) -> Result<sqlx::Transaction<'c, sqlx::Postgres>> {
|
||||
use FlowModuleValue::*;
|
||||
for module in &mut *modules {
|
||||
@@ -2234,6 +2239,7 @@ async fn reduce_flow<'c>(
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
modules,
|
||||
modules_node,
|
||||
)
|
||||
@@ -2247,6 +2253,7 @@ async fn reduce_flow<'c>(
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
&mut branch.modules,
|
||||
&mut branch.modules_node,
|
||||
)
|
||||
@@ -2258,6 +2265,7 @@ async fn reduce_flow<'c>(
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
default,
|
||||
default_node,
|
||||
)
|
||||
@@ -2271,6 +2279,7 @@ async fn reduce_flow<'c>(
|
||||
workspace_id,
|
||||
failure_module,
|
||||
same_worker,
|
||||
preserve_step_tags,
|
||||
&mut branch.modules,
|
||||
&mut branch.modules_node,
|
||||
)
|
||||
|
||||
+1
-1
@@ -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.829.0";
|
||||
export const VERSION = "v1.830.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-chat",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-chat",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@ai-sdk/react": "^4.0.102",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-chat",
|
||||
"description": "Build chat interfaces on Windmill flows deployed in chat mode, from any frontend or raw app",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://github.com/windmill-labs/windmill/tree/main/chat-sdk#readme",
|
||||
|
||||
@@ -10,4 +10,4 @@ export const WM_FORK_PREFIX = "wm-fork";
|
||||
// (e.g. utils.ts) can read it without importing main.ts and creating a circular
|
||||
// dependency (main → workspace → utils → main) that triggers a TDZ.
|
||||
// Re-exported from main.ts for backwards compatibility.
|
||||
export const VERSION = "1.829.0";
|
||||
export const VERSION = "1.830.0";
|
||||
|
||||
@@ -34,10 +34,14 @@ export async function buildPermissionedAsContext(
|
||||
return undefined;
|
||||
}
|
||||
const user = await wmill.whoami({ workspace });
|
||||
// A superadmin acts as admin in every workspace, so the backend preserves
|
||||
// ownership for them even when their own `usr` row there is not an admin.
|
||||
const userIsAdminOrDeployer =
|
||||
user.is_admin || (user.groups ?? []).includes("wm_deployers");
|
||||
user.is_admin ||
|
||||
user.is_super_admin ||
|
||||
(user.groups ?? []).includes("wm_deployers");
|
||||
log.debug(
|
||||
`permissioned_as: user=${user.email}, is_admin=${user.is_admin}, groups=${JSON.stringify(user.groups)}, isAdminOrDeployer=${userIsAdminOrDeployer}`
|
||||
`permissioned_as: user=${user.email}, is_admin=${user.is_admin}, is_super_admin=${user.is_super_admin}, groups=${JSON.stringify(user.groups)}, isAdminOrDeployer=${userIsAdminOrDeployer}`
|
||||
);
|
||||
return {
|
||||
userCache: new Map(),
|
||||
|
||||
@@ -189,6 +189,8 @@ const LANG_ANNOTATIONS: Partial<Record<ScriptLanguage, string[]>> = {
|
||||
"py311",
|
||||
"py312",
|
||||
"py313",
|
||||
"py314",
|
||||
"py315",
|
||||
"sandbox",
|
||||
],
|
||||
bun: ["npm", "nodejs", "native", "nobundling", "sandbox"],
|
||||
@@ -208,7 +210,7 @@ const LANG_ANNOTATIONS: Partial<Record<ScriptLanguage, string[]>> = {
|
||||
*
|
||||
* `# py: <specifier>` is the exception the macro does not cover: the python
|
||||
* import parser reads it directly (`windmill-parser-py-imports`, alongside the
|
||||
* `py310`..`py313` flags) to pick the interpreter, which changes what resolves.
|
||||
* `py310`..`py315` flags) to pick the interpreter, which changes what resolves.
|
||||
*/
|
||||
export function hasLockAffectingAnnotation(
|
||||
scriptContent: string,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Regression guard: a superadmin whose `usr` row in the workspace is not an
|
||||
* admin still preserves ownership on the backend, so the CLI must not refuse
|
||||
* their push as if it would reassign every item to them.
|
||||
*/
|
||||
|
||||
import { expect, test } from "bun:test";
|
||||
import { mockServices } from "./mock_services.ts";
|
||||
|
||||
mockServices({
|
||||
whoami: async () => ({
|
||||
email: "super@windmill.dev",
|
||||
username: "super",
|
||||
is_admin: false,
|
||||
is_super_admin: true,
|
||||
groups: [],
|
||||
}),
|
||||
});
|
||||
|
||||
const { buildPermissionedAsContext } = await import(
|
||||
"../src/core/permissioned_as.ts"
|
||||
);
|
||||
|
||||
test("a superadmin with a non-admin membership counts as able to preserve", async () => {
|
||||
const ctx = await buildPermissionedAsContext("w", "v1");
|
||||
expect(ctx?.userIsAdminOrDeployer).toBe(true);
|
||||
});
|
||||
@@ -15,7 +15,9 @@ RUN RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo /usr/local/cargo/b
|
||||
# ansible there leaves its venv interpreter as a dangling symlink at runtime, so every
|
||||
# ansible-* executable fails with ENOENT ("ansible-galaxy not found"). Pin the tool's
|
||||
# interpreter to a persistent image path so the install stays self-contained.
|
||||
RUN UV_PYTHON_INSTALL_DIR=/usr/local/uv/py uv tool install ansible && [ -d "$(uv tool dir)/ansible/bin/" ] && find "$(uv tool dir)/ansible/bin/" -mindepth 1 -maxdepth 1 -type f -executable -regextype posix-extended -regex '^((.+/)?)[^.]+' -print0 | xargs -0 ln -sf -t "$UV_TOOL_BIN_DIR/" || true
|
||||
# The version is explicit because uv otherwise downloads its newest stable Python, which
|
||||
# ansible-core may not support yet: the install succeeds and every executable fails on import.
|
||||
RUN UV_PYTHON_INSTALL_DIR=/usr/local/uv/py uv tool install --python 3.14 ansible && [ -d "$(uv tool dir)/ansible/bin/" ] && find "$(uv tool dir)/ansible/bin/" -mindepth 1 -maxdepth 1 -type f -executable -regextype posix-extended -regex '^((.+/)?)[^.]+' -print0 | xargs -0 ln -sf -t "$UV_TOOL_BIN_DIR/" || true
|
||||
|
||||
# C#
|
||||
RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \
|
||||
|
||||
@@ -35,7 +35,9 @@ RUN RUSTUP_HOME=/usr/local/rustup CARGO_HOME=/usr/local/cargo /usr/local/cargo/b
|
||||
# ansible there leaves its venv interpreter as a dangling symlink at runtime, so every
|
||||
# ansible-* executable fails with ENOENT ("ansible-galaxy not found"). Pin the tool's
|
||||
# interpreter to a persistent image path so the install stays self-contained.
|
||||
RUN UV_PYTHON_INSTALL_DIR=/usr/local/uv/py uv tool install ansible && [ -d "$(uv tool dir)/ansible/bin/" ] && find "$(uv tool dir)/ansible/bin/" -mindepth 1 -maxdepth 1 -type f -executable -regextype posix-extended -regex '^((.+/)?)[^.]+' -print0 | xargs -0 ln -sf -t "$UV_TOOL_BIN_DIR/" || true
|
||||
# The version is explicit because uv otherwise downloads its newest stable Python, which
|
||||
# ansible-core may not support yet: the install succeeds and every executable fails on import.
|
||||
RUN UV_PYTHON_INSTALL_DIR=/usr/local/uv/py uv tool install --python 3.14 ansible && [ -d "$(uv tool dir)/ansible/bin/" ] && find "$(uv tool dir)/ansible/bin/" -mindepth 1 -maxdepth 1 -type f -executable -regextype posix-extended -regex '^((.+/)?)[^.]+' -print0 | xargs -0 ln -sf -t "$UV_TOOL_BIN_DIR/" || true
|
||||
# dotnet SDK
|
||||
RUN wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh \
|
||||
&& chmod +x dotnet-install.sh \
|
||||
|
||||
@@ -58,7 +58,7 @@ RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmo
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.11.24/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.13.0/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
# Preinstall python runtime to temp location (will copy with world-writable perms later)
|
||||
# --compile-bytecode precompiles the stdlib to .pyc so jobs don't recompile it on every run
|
||||
|
||||
@@ -58,7 +58,7 @@ RUN curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmo
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
# Install UV
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.11.24/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.13.0/uv-installer.sh | sh && mv /root/.local/bin/uv /usr/local/bin/uv
|
||||
|
||||
# Preinstall python runtime to temp location (will copy with world-writable perms later)
|
||||
# --compile-bytecode precompiles the stdlib to .pyc so jobs don't recompile it on every run
|
||||
|
||||
@@ -78,15 +78,16 @@
|
||||
];
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
# Prebuilt V8 binary (must match version in Cargo.toml)
|
||||
# Prebuilt V8 binary (must match the `v8` pin in backend/Cargo.toml).
|
||||
# The simdutf variant: deno_core enables that feature of the v8 crate.
|
||||
# ---------------------------------------------------------------
|
||||
|
||||
rustyV8Archive = let
|
||||
version = "137.1.0";
|
||||
version = "150.4.0";
|
||||
target = stdenv.hostPlatform.rust.rustcTarget;
|
||||
sha256 = {
|
||||
x86_64-linux =
|
||||
"sha256-Tiscfy2bzYGR3s0T+SC1IB3xWvTVpVcSEdjq3MCRoRw=";
|
||||
"sha256-9IdiyhDR8fxgWkQcWuQw7Izh6egPFNePvELLh4wwtHY=";
|
||||
aarch64-linux = lib.fakeHash;
|
||||
x86_64-darwin = lib.fakeHash;
|
||||
aarch64-darwin = lib.fakeHash;
|
||||
@@ -94,7 +95,7 @@
|
||||
in pkgs.fetchurl {
|
||||
name = "librusty_v8-${version}";
|
||||
url =
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_release_${target}.a.gz";
|
||||
"https://github.com/denoland/rusty_v8/releases/download/v${version}/librusty_v8_simdutf_release_${target}.a.gz";
|
||||
inherit sha256;
|
||||
};
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill-labs/components",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:ui-builder": "mv static/ui_builder static/ui_builder.dev-disabled 2>/dev/null || true ; trap 'mv static/ui_builder.dev-disabled static/ui_builder 2>/dev/null || true' EXIT ; vite dev",
|
||||
|
||||
@@ -836,7 +836,7 @@ export const settings: Record<string, Setting[]> = {
|
||||
description: 'Default python version for newly deployed scripts',
|
||||
key: 'instance_python_version',
|
||||
fieldType: 'select_python',
|
||||
placeholder: '3.10,3.11,3.12,3.13',
|
||||
placeholder: '3.10,3.11,3.12,3.13,3.14,3.15',
|
||||
select_items: [
|
||||
{
|
||||
label: 'Instance default',
|
||||
@@ -854,6 +854,12 @@ export const settings: Record<string, Setting[]> = {
|
||||
},
|
||||
{
|
||||
label: '3.13'
|
||||
},
|
||||
{
|
||||
label: '3.14'
|
||||
},
|
||||
{
|
||||
label: '3.15'
|
||||
}
|
||||
],
|
||||
storage: 'setting'
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
wmill = ">=1.829.0"
|
||||
wmill = ">=1.830.0"
|
||||
sendgrid = "*"
|
||||
mysql-connector-python = "*"
|
||||
pymongo = "*"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: '3.0.3'
|
||||
|
||||
info:
|
||||
version: 1.829.0
|
||||
version: 1.830.0
|
||||
title: OpenFlow Spec
|
||||
contact:
|
||||
name: Ruben Fiszel
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
RootModule = 'WindmillClient.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.829.0'
|
||||
ModuleVersion = '1.830.0'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "wmill"
|
||||
version = "1.829.0"
|
||||
version = "1.830.0"
|
||||
description = "A client library for accessing Windmill server wrapping the Windmill client API"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://windmill.dev"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@windmill/windmill",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"exports": "./src/index.ts",
|
||||
"publish": {
|
||||
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts", "./wacError.ts"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"author": "Ruben Fiszel",
|
||||
"license": "Apache 2.0",
|
||||
"homepage": "https://github.com/windmill-labs/windmill/tree/main/typescript-client#readme",
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.829.0
|
||||
1.830.0
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@stoplight/yaml": "^4.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill-yaml-validator",
|
||||
"version": "1.829.0",
|
||||
"version": "1.830.0",
|
||||
"description": "YAML validator for Windmill flow, schedule, and trigger files",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user